The More with symfony book | symfony | Web - Formulaire
Transcription
The More with symfony book | symfony | Web - Formulaire
The More with symfony book | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: About French (fr) You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. "The More with symfony book" is part of the official symfony documentation. Introduction Techniques Avancées de Routage Accroître la Productivité Les Emails Widgets et Validateurs Personnalisés Formulaires Avancés Etendre la Web Debug Toolbar Techniques Avancées avec Doctrine Tirer Profit de l'Héritage de Table avec Doctrine Plonger dans les Entrailles de Symfony Windows et symfony Développer pour Facebook Tirer Profit de la Ligne de Commande Manipuler le Cache de Configuration de symfony Travailler avec la Communauté symfony Appendix B - License Annexe A Code JavaScript du Widget sfWidgetFormGMapAddress Annexe A - Exemple de Script d'Installation Personnalisé A propos des Auteurs A Propos des Traducteurs Be trained by symfony experts Jan 24: Paris Support symfony! Buy this book or donate. (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Search powered by google Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/[31/12/2010 02:33:32] The More with symfony book | Introduction | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Introduction You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Fabien Potencier A l'heure où ces lignes sont écrites, le projet symfony a célébré une étape significative : son quatrième anniversaire. En seulement quatre ans, le framework symfony a grandi pour devenir l'un des frameworks PHP les plus populaires dans le monde et sur lequel s'appuient des sites à fort trafic comme Delicious, Yahoo Bookmarks et Daily Motion. Néanmoins, c'est la sortie récente de symfony 1.4 en novembre 2009 qui marque la fin d'un cycle. Cet ouvrage est le meilleur moyen de terminer ce cycle, et de ce fait, vous vous apprêtez à lire le tout dernier livre officiel de la branche 1.x de symfony, publié par l'équipe du projet symfony. Le prochain livre à paraître se focalisera quant à lui autour de Symfony 2.0, et sera dévoilé à la fin de l'année 2010. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Pour cette raison, et bien d'autres que j'expliquerai dans cette introduction, ce livre est un peu spécial pour nous tous. Pourquoi avoir écrit un nouvel ouvrage ? Nous avons récemment publié deux autres livres sur symfony 1.3 et 1.4: "Practical symfony" et "The symfony reference guide". Le livre pratique est un excellent moyen de démarrer l'apprentissage de symfony dans la mesure où vous apprenez toutes les bases du framework à travers le développement d'un projet réel, étape par étape. Le dernier est un guide de référence qui agrège la plupart des informations de configuration dont vous pourriez avoir besoin au quotidien pour vos développements. "Plus loin avec symfony" est un ouvrage à propos de sujets plus avancés de symfony. Cet ouvrage n'est pas le tout premier que vous devriez lire à propos de symfony, mais il s'avèrera être une aide ultime pour tous ceux qui ont déjà fait leurs preuves sur plusieurs petits projets réalisés avec le framework. Si vous avez déjà voulu savoir comment symfony fonctionne à l'intérieur, ou bien si vous souhaitez étendre le framework de diverses manières en vue de le faire fonctionner pour des besoins spécifiques, alors ce livre est fait pour vous. Dans ce cas précis, "More with symfony" est tout ce dont il faut savoir pour faire évoluer vos compétences symfony au niveau supérieur. Comme cet ouvrage est un recueil de tutoriels à propos de sujets divers, n'hésitez pas à lire les chapitres qui vont suivre dans l'ordre que vous souhaitez, en vous basant sur ce que vous êtes entrain d'essayer d'accomplir avec le framework. A propos de ce livre Cet ouvrage est un peu spécial parce qu'il s'agit d'un livre écrit par la communauté pour la communauté. Plus d'une douzaine de personnes ont contribué à cet ouvrage : des auteurs, aux traducteurs, en passant par les relecteurs, c'est en réalité un incroyable concentré d'efforts qui a été mis en oeuvre pour parvenir à ce livre. Ce livre a été publié simultanément dans pas moins de cinq langues (anglais, français, italien, espagnol et japonais). Tout cela n'aurait été possible sans le courageux travail bénévole des équipes de traduction. Cet ouvrage a été rendu possible grâce à l'esprit Open-Source et c'est aussi pourquoi il est publié sous une licence Open-Source. Ce point à lui seul change tout, car il signifie que personne n'a été payé pour travailler sur ce recueil : tous les contributeurs ont travaillé dur pour y parvenir et c'est surtout parce qu'ils voulaient tous y arriver. Au cours de cette aventure, chaque contributeur a souhaité à la fois partager ses connaissances, contribuer en retour à la communauté, participer à l'évangélisation de symfony et, bien sûr, de prendre du plaisir et devenir célèbre. "Plus loin avec symfony" a été écrit par dix auteurs qui utilisent symfony au quotidien en poste de développeurs ou de chefs de projets. Ces personnes ont toutes une connaissance http://www.symfony-project.org/more-with-symfony/1_4/fr/01-Introduction[31/12/2010 02:33:36] Chapter Content Pourquoi avoir écrit un nouvel ouvrage ? A propos de ce livre Remerciements Avant de commencer Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine The More with symfony book | Introduction | symfony | Web PHP Framework particulièrement étendue du framework, et ont souhaité partager leur savoir et leur expérience dans ces chapitres. - Français) May 23: Paris (Maîtrise de & Doctrine - Français) Remerciements and more... Lorsque j'ai commencé à réfléchir comment écrire un nouveau livre à propos de symfony en août 2009, j'ai immédiatement eu une idée folle : pourquoi ne pas écrire un livre en deux mois seulement et le publier simultanément en cinq langues ! Il va de soi qu'impliquer la communauté dans un projet d'une telle ampleur fut presque obligatoire. J'ai commencé par parler de cette idée au Japon pendant la conférence PHP, et en quelques heures seulement, l'équipe de traduction japonaise était déjà prête à travailler. C'était incroyable ! La réponse des auteurs et des traducteurs était elle aussi très encourageante, et peu de temps après, "Plus loin avec symfony" était né. Je souhaite remercie toutes les personnes qui ont participé d'une manière ou d'une autre à la création de cet ouvrage, et notamment, sans ordre particulier, les personnes ci-dessous : Ryan Weaver, Geoffrey Bachelet, Hugo Hamon, Jonathan Wage, Thomas Rabaix, Fabrice Bernhard, Kris Wallsmith, Stefan Koopmanschap, Laurent Bonnet, Julien Madelin, Franck Bodiot, Javier Eguiluz, Nicolas Ricci, Fabrizio Pucci, Francesco Fullone, Massimiliano Arione, Daniel Londero, Xavier Briand, Guillaume Bretou, Akky Akimoto, Hidenori Goto, Hideki Suzuki, Katsuhiro Ogawa, Kousuke Ebihara, Masaki Kagaya, Masao Maeda, Shin Ohno, Tomohiro Mitsumune, et Yoshihiro Takahara. Avant de commencer Ce livre a été écrit pour les versions 1.3 et 1.4 de symfony. Comme écrire un livre unique pour deux versions différentes d'un logiciel est une tâche peu commune, cette section explique quelles sont les principales différences entre les deux versions, et comment faire le meilleur choix pour vos projets. Les versions 1.3 et 1.4 de symfony ont toutes les deux été publiées à peu près au même moment (à la fin de l'année 2009). Par conséquent, elles ont toutes les deux le même jeu de fonctionnalités. La seule différence qui les oppose concerne le support de la rétrocompatibilité avec les versions plus anciennes de symfony. Symfony 1.3 est la distribution que vous souhaiterez utiliser si vous avez besoin de migrer un projet ancien qui repose sur une version antérieure de symfony (1.0, 1.1 ou 1.2). Cette version intègre une couche de compatibilité rétrograde et toutes les fonctionnalités qui ont été rendues obsolètes pendant le cycle de développement de la branche 1.3 restent disponibles. Par conséquent, cela signifie aussi que mettre à jour un projet est facile, simple et sûr. En revanche, si vous démarrez un nouveau projet aujourd'hui, vous devriez utiliser directement symfony 1.4. Cette version a exactement le même jeu de fonctionnalités que symfony 1.3 à la différence que les fonctionnalités obsolètes, y compris la couche entière de compatibilité rétrograde, ont été supprimées. Cette version est donc plus saine et aussi légèrement plus rapide que symfony 1.3. Un autre avantage non négligeable d'utiliser symfony 1.4 est son support à plus long terme. En devenant une version supportée à long terme (LTS - Long Time Support), symfony 1.4 sera maintenue par l'équipe de développement pendant trois ans, jusqu'en novembre 2012. Bien évidemment, vous pouvez migrer vos projets vers symfony 1.3 et puis mettre à jour votre code petit à petit afin de supprimer les fonctionnalités obsolètes, et éventuellement poursuivre la migration vers symfony 1.4 pour bénéficier de son support à long terme. Vous avez encore tout le temps de planifier votre migration vers symfony 1.3 puisque cette dernière sera supportée pendant un an, jusqu'en novembre 2010. Enfin, comme cet ouvrage ne présente aucune fonctionnalité obsolète, tous les exemples que vous découvrirez fonctionneront exactement de la même manière sur les deux versions. Techniques Avancées de Routage » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. http://www.symfony-project.org/more-with-symfony/1_4/fr/01-Introduction[31/12/2010 02:33:36] Search powered by google The More with symfony book | Introduction | symfony | Web PHP Framework Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/01-Introduction[31/12/2010 02:33:36] The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Techniques Avancées de Routage You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Ryan Weaver En son noyau, le framework de routage est en réalité une carte qui relie chaque URL à une destination spécifique à l'intérieur d'un projet symfony et vice versa. Cet outil permet de créer des URLs propres et élégantes, et qui restent complètement indépendantes de la logique applicative. Ces améliorations ont été réalisées depuis les versions récentes de symfony, et le framework de routing continue encore d'aller plus loin aujourd'hui. Ce chapitre décrira comment créer une application web simple pour laquelle chaque client utilise un sous-domaine séparé, par exemple client1.mydomain.com et client2.mydomain.com. C'est en étendant le framework de routage que cette tâche devient très facile à réaliser. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Ce chapitre nécessite l'usage de Doctrine en guise de couche d'ORM pour le projet symfony. Initialisation du Projet : Un CMS pour Plusieurs Clients Dans ce chapitre, une société fictive - Sympal Builder - désire mettre en place un CMS permettant à ses clients de construire leur propre site web et l'héberger sur un sous-domaine de sympalbuilder.com. L'objectif consiste ainsi à rendre le site grand public accessible à l'adresse xxx.sympalbuilder.com pour le client XXX, et l'interface d'administration correspondante à l'adresse xxx.sympalbuilder.com/backend.php. Le nom Sympal est emprunté du nom de l'application Sympal développée par Jonathan Wage. Sympal est un framework de gestion de contenu bâti sur un socle technique symfony et Doctrine. Ce projet dispose de deux besoins fonctionnels basiques et obligatoires : Les utilisateurs doivent être capables de créer des pages web et spécifier pour chacune d'entre elles un titre, un contenu et une URL correspondante. L'application entière doit être construite à l'intérieur d'un seul projet symfony qui gère à la fois les applications frontend et backend de tous les clients du site. La détermination du client est basée sur le sous-domaine demandé et afin de permettre le chargement des bonnes informations dans l'application. Pour créer cette application, le serveur web aura besoin de rediriger toutes les requêtes des sous-domaines *.sympalbuilder.com vers le même document racine, le répertoire web du projet symfony. Chapter Content Initialisation du Projet : Un CMS pour Plusieurs Clients Le Modèle de Données et les Données Le Modèle de Données et les Données La base de données du projet se compose de deux tables Client et Page. Chaque Client est représenté par un site (et donc un sous-domaine) qui contient plusieurs objets de type Page. # config/doctrine/schema.yml Client: columns: name: string(255) subdomain: string(50) indexes: subdomain_index: fields: [subdomain] type: unique http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] Le Routage Mécanismes Internes du Framework de Routage Le Gestionnaire de Configuration du Cache du Routage Faire Correspondre une Requête Entrante à une Route Spécifique Créer une Classe de Route Personnalisée Ajouter de la Logique à la Route Personnalisée Profiter de la Route Personnalisée Générer la Bonne Route Les Collections de Routes The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework Page: columns: title: string(255) slug: string(255) content: clob client_id: integer relations: Client: alias: Client foreignAlias: Pages onDelete: CASCADE indexes: slug_index: fields: [slug, client_id] type: unique Bien que les indexes sur chaque table ne soient pas nécessaires, il est tout de même important de les définir dans la mesure où la base de données sera fréquemment interrogée sur ces colonnes. Remplacer les Routes par une Collection de Routes Créer une Classe Personnalisée de Collection de Routes La Pièce Manquante : Créer une Nouvelle Page Personnaliser la Collection de Routes d'Objet Options d'une Collection de Routes Les Routes d'Actions Changer la Colonne Discriminante Modifier les Méthodes du Modèle Modifier les Paramètres par Défaut Conclusion Be trained by symfony experts Jan 24: Paris Feb 21: Paris Pour commencer à donner naissance au projet, il est nécessaire d'avoir dès le départ quelques jeux de données de test dans le fichier data/fixtures/fixtures.yml : # data/fixtures/fixtures.yml Client: client_pete: name: Pete's Pet Shop subdomain: pete client_pub: name: City Pub and Grill subdomain: citypub Page: page_pete_location_hours: title: Location and Hours | Pete's Pet Shop content: We're open Mon - Sat, 8 am - 7pm slug: location Client: client_pete page_pub_menu: title: City Pub And Grill | Menu content: Our menu consists of fish, Steak, salads, and more. slug: menu Client: client_pub Ce jeu de données de test déclare initialement deux sites web constitués chacun d'une seule page. L'url complète vers chaque page est définie à l'aide des deux colonnes subdomain et slug de l'objet de modèle Page. http://pete.sympalbuilder.com/location http://citypub.sympalbuilder.com/menu Le Routage Chaque page d'un site Sympal Builder correspond directement à un objet de modèle Page pour lequel sont définis un titre et un contenu à afficher. L'étape suivante consiste à relier une URL à un objet Page de l'application. Pour ce faire, il suffit de créer une route d'objet de type sfDoctrineRoute qui s'appuie sur la colonne slug. Ainsi, le code suivant recherchera automatiquement un objet Page dans la base de données, pour lequel la valeur du champ slug correspond à celui transmis dans l'url : # apps/frontend/config/routing.yml page_show: url: /:slug class: sfDoctrineRoute options: model: Page type: object params: module: page http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] (Maîtrise de & Doctrine - Français) (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Search powered by google The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework action: show La route ci-dessus correspondra automatiquement à l'url http://pete.sympalbuilder.com/location ainsi qu'à son objet Page associé. Malheureusement, cette route correspondra aussi à l'url http://pete.sympalbuilder.com/menu, ce qui signifie que le menu du restaurant sera affiché sur le site de Pete ! Par conséquent, la route est encore incapable de faire la différence entre les sous-domaines des différents clients. Pour concrétiser cette application, la route a besoin d'être plus intelligente. Elle devrait en effet correspondre à une Page en se basant à la fois sur la valeur de la colonne slug et sur celle de la colonne client_id. En d'autres termes, il s'agit de faire correspondre l'hôte (par exemple pete.sympalbuilder.com) avec la colonne subdomain du modèle Client. Pour y parvenir, il suffit d'étendre le framework de routage en créant une classe personnalisée de route. Néanmoins, avant de démarrer, il convient de rappeler quelques principes de base au sujet du fonctionnement du système de routage de symfony. Mécanismes Internes du Framework de Routage Dans symfony, une route est un objet de type sfRoute qui dispose de deux rôles importants : Générer une URL : par exemple, si un paramètre slug est passé à une règle page_show, alors la route doit être capable de générer une véritable URL correspondante (/location par exemple). Reconnaître une URL entrante : en lui fournissant l'url d'une requête entrante, chaque route doit être capable de déterminer si l'URL correspond aux contraintes de la route. Les informations de chaque route individuelle sont généralement déclarées à l'intérieur de chaque répertoire config/ d'une application, et plus précisément dans le fichier app/yourappname/config/routing.yml. Il est important de se rappeler que chaque route est un objet de type sfRoute. Par conséquent, comment ces quelques données YAML simples deviennent de véritables objets sfRoute ? Le Gestionnaire de Configuration du Cache du Routage En dépit du fait que la plupart des routes sont définies dans un fichier YAML, chaque entrée de ce fichier est en réalité transformée en un objet au traitement de la requête. Cette transformation est assurée à l'aide d'une classe spéciale plus communément intitulée gestionnaire de configuration de cache. A l'issue de la conversion, il en résulte du code PHP représentant toutes les routes de l'application. Bien que les spécificités de ce mécanisme dépassent le périmètre de ce chapitre, il n'en demeure pas moins intéressant de tirer quelques informations de la version compilée de la route page_show. Le code compilé se trouve en effet dans le fichier cache/yourappname/envname/config/config_routing.yml.php pour chaque application et pour chaque environnement. Le listing ci-dessous est une version simplifiée de ce à quoi ressemble la route page_show : new sfDoctrineRoute('/:slug', array ( 'module' => 'page', 'action' => 'show', ), array ( 'slug' => '[^/\\.]+', ), array ( 'model' => 'Page', 'type' => 'object', )); Le nom de la classe de chaque route est défini grâce à la clé class du fichier routing.yml. Si aucune clé class n'est spécifiée, la route par défaut sera issue de la classe sfRoute. Une autre classe spécifique plus commune est sfRequestRoute qui offre au développeur la possibilité de créer des routes dites RESTful. Une liste complète des classes de route et des options disponibles existe dans le guide de référence de symfony. Faire Correspondre une Requête Entrante à une Route Spécifique L'un des principaux rôle du framework de routage consiste à faire correspondre chaque URL entrante avec le bon objet route. C'est la classe sfPatternRouting qui représente le moteur du noyau du routage et qui est dédiée à cette tâche. En dépit de son importance, un développeur http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework n'interagira que très rarement avec sfPatternRouting. Pour faire correspondre la bonne route, la classe sfPatternRouting itère sur chaque objet sfRoute et demande à la route si elle répond à l'url entrante. Intérieurement, cela signifie que sfPatternRouting appelle la méthode sfMethod::matchesUrl() sur chaque objet route. Cette méthode retourne simplement false si la route ne correspond pas à l'url entrante. A contrario, si la route répond parfaitement à l'URL entrante, la méthode sfRoute::matchesUrl() ne renverra pas seulement la valeur true. Bien au contraire, la route retourne un tableau des paramètres qui sont ensuite fusionnés à l'intérieur de l'objet représentant la requête. Par exemple, l'url http://pete.sympalbuilder.com/location répond à la route page_show, dont la méthode matchesUrl() retourne alors le tableau suivant : array('slug' => 'location') Cette information est ensuite fusionnée dans l'objet de requête, et c'est pourquoi les paramètres de la route (slug par exemple) sont accessibles depuis le fichier d'actions. $this->slug = $request->getParameter('slug'); Comme on peut s'en douter, surcharger ou bien redéfinir complètement la méthode sfRoute::matchesUrl() est un excellent moyen d'étendre et de personnaliser une route afin de réaliser presque tout ce que l'on souhaite. Créer une Classe de Route Personnalisée L'étape suivante consiste à présent à créer une nouvelle classe de route personnalisée dans le but d'étendre la route page_show actuelle afin qu'elle puisse se baser sur le sous-domaine des objets Client. Pour ce faire, il suffit de créer un nouveau fichier nommé acClientObjectRoute.class.php et de le placer dans le répertoire lib/routing du projet en ayant pris le soin de créer ce répertoire juste avant : // lib/routing/acClientObjectRoute.class.php class acClientObjectRoute extends sfDoctrineRoute { public function matchesUrl($url, $context = array()) { if (false === $parameters = parent::matchesUrl($url, $context)) { return false; } return $parameters; } } Après avoir vidé le cache de symfony, il ne reste plus qu'à indiquer à la route page_show d'utiliser cette classe de route en modifiant la valeur de la clé class de la définition de la route dans le fichier routing.yml : # apps/fo/config/routing.yml page_show: url: /:slug class: acClientObjectRoute options: model: Page type: object params: module: page action: show Pour l'instant, la classe acClientObjectRoute n'apporte aucune fonctionnalité supplémentaire mais toutes les pièces du puzzle sont désormais en place. La section suivante explique quels sont les deux rôles spécifiques de la méthode matchesUrl() et donne la démarche à suivre pas à pas pour redéfinir la logique de cette dernière. Ajouter de la Logique à la Route Personnalisée Pour ajouter la fonctionnalité requise à la classe de route personnalisée, il suffit de remplacer le contenu du fichier acClientObjectRoute.class.php par le code ci-dessous. class acClientObjectRoute extends sfDoctrineRoute { http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework protected $baseHost = '.sympalbuilder.com'; public function matchesUrl($url, $context = array()) { if (false === $parameters = parent::matchesUrl($url, $context)) { return false; } // return false if the baseHost isn't found if (strpos($context['host'], $this->baseHost) === false) { return false; } $subdomain = str_replace($this->baseHost, '', $context['host']); $client = Doctrine_Core::getTable('Client') ->findOneBySubdomain($subdomain) ; if (!$client) { return false; } return array_merge(array('client_id' => $client->id), $parameters); } } Le premier appel à la méthode parent::matchesUrl() est important puisqu'il exécute le processus classique d'analyse de la route. Dans cet exemple, tant que l'URL /location répond à la route page_show, la méthode parent::matchesUrl() doit retourner un tableau contenant le paramètre slug correspondant. array('slug' => 'location') En d'autres termes, tout le travail complexe d'analyse de la route a été réalisé par le framework, ce qui permet au reste de la méthode de se concentrer sur l'analyse de la correspondance avec le sous-domaine du Client. public function matchesUrl($url, $context = array()) { // ... $subdomain = str_replace($this->baseHost, '', $context['host']); $client = Doctrine_Core::getTable('Client') ->findOneBySubdomain($subdomain) ; if (!$client) { return false; } return array_merge(array('client_id' => $client->id), $parameters); } En exécutant un simple remplacement de chaine, il est possible d'isoler la portion de l'hôte issu du sous-domaine, afin d'interroger ensuite la base de données pour vérifier si un objet Client a ce sous-domaine. Si aucun client ne possède ce sous-domaine, alors la méthode retourne false afin d'indiquer que la requête entrante ne correspond pas à la route. En revanche, si un objet Client répondant au sous-domaine courant existe dans la base de données, alors un tableau contenant un paramètre supplémentaire client_id est fusionné avec le tableau original retourné. Le tableau $context passé à la méthode matchesUrl() est prérempli d'informations utiles concernant la requête courante, incluant l'hôte (host), un booléen is_secure, le request_uri, la méthode (method) HTTP et bien plus encore. http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework Ceci étant fait, on peut se demander ce que la nouvelle classe de route personnalisée a vraiment accompli. La classe acClientObjectRoute réalise désormais les tâches suivantes : La variable entrante $url correspondra à la route uniquement si l'hôte contient un sousdomaine appartenant à l'un des objets Client du modèle. Si la route répond, alors un paramètre additionnel client_id pour l'objet de modèle correspondant est retourné dans le tableau, qui sera ensuite fusionné dans les paramètres de la requête. Profiter de la Route Personnalisée Maintenant que le bon paramètre client_id est retourné par la classe acClientObjectRoute, il devient alors naturellement accessible via l'objet de la requête. Par exemple, l'action page/show peut ainsi utiliser le paramètre client_id pour retrouver l'objet Page correct : public function executeShow(sfWebRequest $request) { $this->page = Doctrine_Core::getTable('Page')->findOneBySlugAndClientId( $request->getParameter('slug'), $request->getParameter('client_id') ); $this->forward404Unless($this->page); } La méthode findOneBySlugAndClientId() est un nouveau type de finders magiques introduit dans Doctrine 1.2 qui permet d'interroger une table en s'appuyant sur plusieurs champs. Aussi simple que cela puisse paraître, le framework de routage propose une solution encore plus élégante. Il suffit dans un premier temps d'ajouter la méthode ci-dessous à la classe acClientObjectRoute. protected function getRealVariables() { return array_merge(array('client_id'), parent::getRealVariables()); } Avec cette dernière pièce du puzzle, l'action peut s'appuyer entièrement sur la route pour retourner un objet Page correspondant. L'action page/show peut ainsi être réduite à seulement une ligne. public function executeShow(sfWebRequest $request) { $this->page = $this->getRoute()->getObject(); } Sans aucun effort additionnel, le code ci-dessus interrogera la base de données pour récupérer un objet Page en s'appuyant sur les deux colonnes slug et client_id. De plus, comme toutes les routes d'objet, l'action est automatiquement redirigée vers une page d'erreur 404 si aucun objet correspondant n'a été trouvé. Mais comment tout cela fonctionne-t-il ? Les routes d'objet comme sfDoctrineRoute, dont la classe acClientObjectRoute hérite, interrogent automatiquement la base de données afin de récupérer l'objet correspondant aux valeurs des variables spécifiées à la clé url de la route. Par exemple, la route page_show, qui contient la variable slug dans son url, demande à la base de données de lui donner l'objet Page en s'aidant de la colonne slug. Or dans cette application, la route page_show doit également s'appuyer sur la valeur de la colonne client_id afin de récupérer les objets correspondants. Pour ce faire, il aura seulement fallu surcharger la méthode sfObjectRoute::getRealVariables() qui est automatiquement appelée à l'intérieur de l'objet pour déterminer sur quelles colonnes la requête SQL doit être exécutée. En ajoutant le champ client_id au tableau, l'objet acClientObjectRoute interrogera la base de données en se basant sur ces deux colonnes slug et client_id. Les routes d'objets ignorent automatiquement toutes les variables qui ne correspondent pas à des colonnes réelles. Par exemple, si la clé url contient une variable :page tandis qu'aucune colonne page n'existe dans la table associée, alors cette variable sera ignorée. http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework En l'état actuel des choses, la classe de route personnalisée accomplit tout ce qui est nécessaire sans trop d'effort. Cette nouvelle route sera réutilisée dans les prochaines sections pour créer une interface d'administration spécifique à chaque client. Générer la Bonne Route Un seul petit problème subsiste avec la manière dont est générée l'url. Pour comprendre, il suffit de regarder l'exemple suivant qui décrit la création d'un lien vers une page. <?php echo link_to('Locations', 'page_show', $page) ?> Url générée: /location?client_id=1 Comme on peut le constater, le paramètre client_id a été automatiquement ajouté à la fin de l'url. Cela se produit en effet parce que la route essaie d'utiliser toutes les variables pour générer l'url. Comme la route sait qu'elle doit utiliser les deux paramètres slug et client_id, alors elle les ajoute à l'url qu'elle génère. Pour fixer cette petite contrariété, il suffit d'ajouter la méthode suivante à la classe acClientObjectRoute. protected function doConvertObjectToArray($object) { $parameters = parent::doConvertObjectToArray($object); unset($parameters['client_id']); return $parameters; } Lorsqu'une route d'objet est générée, elle s'attend à retrouver toutes les informations nécessaires en appelant la méthode doConvertObjectToArray(). Par défaut, le paramètre client_id est retourné dans le tableau $parameters. Désormais, en supprimant ce paramètre du tableau, cela permet ainsi d'éviter de le voir réapparaître dans l'url générée. Il est important de se souvenir que la route d'objet peut s'offrir ce luxe dans la mesure où l'information du Client est contenue dans le sous-domaine lui-même. Le traitement de la méthode doConvertObjectToArray() peut entièrement être redéfini et géré par les soins du développeur en ajoutant une méthode toParams() à la classe de modèle. Cette méthode se doit de retourner un tableau des paramètres qui doivent figurer au moment de la génération de la route. Les Collections de Routes Pour en finir avec l'application Sympal Builder, une interface d'administration doit être créée dans laquelle chaque Client individuel sera capable de gérer ses propres Pages. Pour ce faire, l'application a besoin d'un jeu d'actions pour lister, créer, éditer et supprimer des objets Page. Comme tous ces types de modules sont sensiblement génériques, symfony est capable de les générer automatiquement. Il suffit pour ce faire d'exécuter la tâche suivante depuis la ligne de commande afin de générer un module pageAdmin à l'intérieur d'une application backend qui aura été créée juste avant. $ php symfony doctrine:generate-module backend pageAdmin Page --with-doctrine-route -with-show La tâche ci-dessus génère un module avec un fichier d'actions et ses vues associées capables de réaliser toutes les modifications nécessaires sur n'importe quel objet Page. De nombreuses personnalisations de ce module CRUD généré peuvent être réalisées mais cela sort du cadre de ce chapitre. Alors que la commande ci-dessus prépare le module pour le développeur, il reste encore une route à créer pour chaque action. En passant l'option --with-doctrine-route à la commande, chaque action a été générée pour fonctionner avec une route d'objet. Cela réduit considérablement la taille du code dans chaque action. Par exemple, l'action edit contient à présent seulement une simple ligne. public function executeEdit(sfWebRequest $request) { $this->form = new PageForm($this->getRoute()->getObject()); } Au total, l'application d'administration de Sympal Builder contient seulement les actions index, http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework new, create, edit, update, et delete. Dans un schéma classique de développement, créer ces routes en vue d'une utilisation RESTful aurait obligé le développeur à davantage de configuration dans le fichier routing.yml. pageAdmin: url: /pages class: sfDoctrineRoute options: { model: Page, type: list } params: { module: page, action: index requirements: sf_method: [get] pageAdmin_new: url: /pages/new class: sfDoctrineRoute options: { model: Page, type: object } params: { module: page, action: new } requirements: sf_method: [get] pageAdmin_create: url: /pages class: sfDoctrineRoute options: { model: Page, type: object } params: { module: page, action: create requirements: sf_method: [post] pageAdmin_edit: url: /pages/:id/edit class: sfDoctrineRoute options: { model: Page, type: object } params: { module: page, action: edit } requirements: sf_method: [get] pageAdmin_update: url: /pages/:id class: sfDoctrineRoute options: { model: Page, type: object } params: { module: page, action: update requirements: sf_method: [put] pageAdmin_delete: url: /pages/:id class: sfDoctrineRoute options: { model: Page, type: object } params: { module: page, action: delete requirements: sf_method: [delete] pageAdmin_show: url: /pages/:id class: sfDoctrineRoute options: { model: Page, type: object } params: { module: page, action: show } requirements: sf_method: [get] } } } } Enfin, pour connaître et visualiser la configuration de toutes les routes déclarées dans une application, il suffit d'exécuter la tâche app:routes dans la console afin d'obtenir un résumé de la définition de chaque route. $ php symfony app:routes backend >> app Current routes for application "backend" Name Method Pattern pageAdmin GET /pages pageAdmin_new GET /pages/new pageAdmin_create POST /pages pageAdmin_edit GET /pages/:id/edit pageAdmin_update PUT /pages/:id pageAdmin_delete DELETE /pages/:id pageAdmin_show GET /pages/:id Remplacer les Routes par une Collection de Routes http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework Heureusement, symfony fournit une manière bien plus simple pour spécifier toutes les routes qui appartiennent à un CRUD traditionnel. Par conséquent, tout le contenu actuel du fichier routing.yml se résume à une seule et unique route. pageAdmin: class: options: model: prefix_path: module: sfDoctrineRouteCollection Page /pages pageAdmin Une fois encore, l'exécution de la tâche app:routes permet de visualiser toutes les routes définies. Comme on peut le constater, les sept routes précédentes existent toujours. $ php symfony app:routes backend >> app Current routes for application "backend" Name Method Pattern pageAdmin GET /pages.:sf_format pageAdmin_new GET /pages/new.:sf_format pageAdmin_create POST /pages.:sf_format pageAdmin_edit GET /pages/:id/edit.:sf_format pageAdmin_update PUT /pages/:id.:sf_format pageAdmin_delete DELETE /pages/:id.:sf_format pageAdmin_show GET /pages/:id.:sf_format Les collections de routes sont un type particulier de routes d'objet qui représentent intérieurement plus d'une seule route. La route sfDoctrineRouteCollection, par exemple, génère automatiquement les sept routes les plus fréquentes nécessaires à un CRUD. En coulisses, la classe sfDoctrineRouteCollection ne fait rien de plus que créer les sept même routes définies précédemment dans le fichier routing.yml. Les collections de route existent de base comme raccourci pour créer un groupe de routes communes. Créer une Classe Personnalisée de Collection de Routes A partir de maintenant, chaque Client sera capable de modifier ses propres objets Page par l'intermédiaire d'un CRUD interne fonctionnel et accessible depuis l'URL /pages. Malheureusement, chaque Client peut pour le moment voir et modifier tous les objets Page de la base de données. Par exemple, l'URL http://pete.sympalbuilder.com/backend.php/pages génèrera une liste de deux objets Page issus des jeux de données de test : la page location de la boutique Pete's Pet Shop et le menu de la page Menu de City Pub. Ce problème peut être corrigé en réutilisant la classe acClientObjectRoute créée précédemment pour le frontend. La classe sfDoctrineRouteCollection génère un groupe d'objets sfDoctrineRoute mais il s'agit ici de générer un groupe d'objets acClientObjectRoute à la place. Pour y parvenir, il sera nécessaire d'avoir recours à une classe personnalisée de collection de routes. Il suffit alors de créer le nouveau fichier acClientObjectRouteCollection.class.php dans le répertoire lib/routing du projet avant de lui attribuer le contenu ci-dessous. On remarque au passage que ce code est particulièrement concis. // lib/routing/acClientObjectRouteCollection.class.php class acClientObjectRouteCollection extends sfObjectRouteCollection { protected $routeClass = 'acClientObjectRoute'; } La propriété $routeClass définit la classe utilisée pour identifier chaque route sous-jacente. C'est tout ! Chaque route sous-jacente est désormais un objet de type acClientObjectRoute. Ainsi, la page http://pete.sympalbuilder.com/backend.php/pages affichera uniquement une seule page : la page location du magasin animalier Pet's Pet Shop. Grâce à cette nouvelle classe de route personnalisée, l'action index retourne uniquement les objets Page liés au Client correspondant, en se basant toujours sur le sous-domaine de la requête. Avec seulement quelques lignes de code, un module entier d'administration des objets Page a été créé, et ce celui-ci peut désormais être utilisé en toute sécurité par les multiples clients. La Pièce Manquante : Créer une Nouvelle Page http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework Pour le moment, le formulaire de création ou d'édition d'une page affiche une liste déroulante de tous les objets Client de la base de données. Pour des raisons évidentes de sécurité et d'ergonomie, il n'est pas question de laisser l'utilisateur choisir cette donnée. Il s'agit donc à présent de découvrir comment affecter automatiquement l'objet Client à la page en cours en se basant toujours sur le sous-domaine de la requête. Pour ce faire, il suffit de mettre à jour l'objet PageForm situé dans le fichier lib/form/PageForm.class.php. public function configure() { $this->useFields(array( 'title', 'content', )); } La liste déroulante a été retirée de tous les formulaires Page comme cela était souhaité. Néanmoins, lorsque de nouveaux objets Page sont créés, le champ client_id reste quant à lui vide. Pour corriger ce défaut, il convient d'associer manuellement l'objet Client correspondant à l'objet Page dans les deux actions new et create. public function executeNew(sfWebRequest $request) { $page = new Page(); $page->Client = $this->getRoute()->getClient(); $this->form = new PageForm($page); } On remarque ici l'introduction d'une nouvelle méthode getClient() qui n'existe pas encore dans la classe acClientObjectRoute. Le code suivant présente l'implémentation de cette nouvelle méthode en réalisant seulement quelques modifications. // lib/routing/acClientObjectRoute.class.php class acClientObjectRoute extends sfDoctrineRoute { // ... protected $client = null; public function matchesUrl($url, $context = array()) { // ... $this->client = $client; return array_merge(array('client_id' => $client->id), $parameters); } public function getClient() { return $this->client; } } En ajoutant une propriété $client à la classe et en la définissant dans la méthode matchesUrl(), l'objet Client correspondant peut alors facilement être rendu accessible grâce à la route. La colonne client_id des nouveaux objets Page sera automatiquement et correctement définie d'après le sous-domaine de l'hôte courant. Personnaliser la Collection de Routes d'Objet En utilisant le framework de routage, les problématiques soulevées par le cahier des charges de l'application Sympal Builder ont toutes été résolues. A mesure que l'application grandit, le développeur sera capable de réutiliser les routes personnalisées pour d'autres modules dans l'interface d'administration. Par exemple, chaque Client pourra gérer ses propres galeries de photos. Une autre raison récurrente qui encourage la création d'une collection de routes personnalisée est l'ajout de routes fréquemment utilisées. On peut par exemple imaginer un projet qui emploie plusieurs modèles, et que chacun d'eux dispose d'une colonne is_active dans leur table respective. Dans l'interface d'administration, cela permettrait ainsi de définir une manière aisée pour activer la valeur de la colonne is_active pour n'importe quel objet. Il s'agit donc tout http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework d'abord de modifier la classe acClientObjectRouteCollection et de lui indiquer d'ajouter une nouvelle route dans sa collection. // lib/routing/acClientObjectRouteCollection.class.php protected function generateRoutes() { parent::generateRoutes(); if (isset($this->options['with_is_active']) && $this->options['with_is_active']) { $routeName = $this->options['name'].'_toggleActive'; $this->routes[$routeName] = $this->getRouteForToggleActive(); } } La méthode sfObjectRouteCollection::generateRoutes() est appelée lorsque la classe de collection de routes est instanciée, et est aussi responsable de la création de toutes les routes nécessaires et de leur ajout dans le tableau de la propriété $routes. Dans ce cas, la création actuelle de la route est déléguée à une nouvelle méthode protégée appelée getRouteForToggleActive(). protected function getRouteForToggleActive() { $url = sprintf( '%s/:%s/toggleActive.:sf_format', $this->options['prefix_path'], $this->options['column'] ); $params = array( 'module' => $this->options['module'], 'action' => 'toggleActive', 'sf_format' => 'html' ); $requirements = array('sf_method' => 'put'); $options = array( 'model' => $this->options['model'], 'type' => 'object', 'method' => $this->options['model_methods']['object'] ); return new $this->routeClass( $url, $params, $requirements, $options ); } La dernière étape restante consiste à configurer la collection de routes dans le fichier routing.yml. On remarque au passage que la méthode generateRoutes() cherche après une option intitulée with_is_active avant d'ajouter la nouvelle route. Ajouter cette logique à la classe donne davantage de contrôle au développeur dans le cas où il souhaiterait utiliser la classe acClientObjectRouteCollection autre part dans le futur, sans pour autant avoir besoin de la route toggleActive. # apps/frontend/config/routing.yml pageAdmin: class: acClientObjectRouteCollection options: model: Page prefix_path: /pages module: pageAdmin with_is_active: true L'exécution de la tâche app:routes permet de vérifier que la nouvelle route toggleActive est présente. La dernière pièce du puzzle manquante est la création de l'action qui s'occupera de cette fonctionnalité. Par ailleurs, si cette collection de routes est amenée à être réutilisée à http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework travers d'autres modules, il suffit simplement de créer un nouveau fichier backendActions.php dans le répertoire apps/backend/lib/actions du projet. Le répertoire actions doit être créé manuellement. # apps/backend/lib/action/backendActions.class.php class backendActions extends sfActions { public function executeToggleActive(sfWebRequest $request) { $obj = $this->getRoute()->getObject(); $obj->is_active = !$obj->is_active; $obj->save(); $this->redirect($this->getModuleName().'/index'); } } Enfin, il ne reste plus qu'à changer la classe de base de la classe pageAdminActions afin que cette dernière hérite de la nouvelle classe backendActions. class pageAdminActions extends backendActions { // ... } Quelles sont les étapes accomplies ? En ajoutant une route à la collection de routes ainsi qu'une action associée dans une classe d'actions de base ; n'importe quel module est désormais capable d'utiliser automatiquement cette fonctionnalité en utilisant une route acClientObjectRouteCollection et en étendant la classe backendActions. Par conséquent, une fonctionnalité commune peut être facilement mutualisée et capitalisée à travers plusieurs modules. Options d'une Collection de Routes Les collections de routes d'objet contiennent une série d'options qui assurent une personnalisation pointue. En de nombreuses circonstances, un développeur peut ainsi utiliser ces options afin de configurer la collection, sans avoir besoin de créer une nouvelle classe personnalisée de collection de routes. Une liste détaillée des options des collections de routes est disponible dans le Guide de Référence de symfony. Les Routes d'Actions Chaque collection de routes d'objet accepte trois options différentes qui déterminent les routes exactes générées dans la collection. Sans pour autant entrer profondément dans le détail, la collection suivante génère les sept routes par défaut, auxquelles s'ajoutent une route de collection d'objets et une route d'objet. pageAdmin: class: acClientObjectRouteCollection options: # ... actions: [list, new, create, edit, update, delete, show] collection_actions: indexAlt: [get] object_actions: toggle: [put] Changer la Colonne Discriminante Par défaut, le framework de routage de symfony utilise la clé primaire d'un modèle lorsqu'il s'agit d'interroger la base de données pour récupérer des objets. Cette information peut bien évidemment être modifiée facilement. Par exemple, le code ci-dessous s'appuiera sur la colonne slug pour récupérer un objet au lieu de sa clé primaire. pageAdmin: class: acClientObjectRouteCollection options: # ... column: slug http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework Modifier les Méthodes du Modèle La route récupère par défaut tous les objets en relation pour une route de la collection et interroge la base de données sur la colonne (column) spécifiée lorsqu'il s'agit des routes d'objet. Une fois de plus, ce comportement peut être modifié et surchargé en ajoutant l'option model_methods à la route. Dans l'exemple ci-dessous, les méthodes fetchAll() et findForRoute() devront être définies dans la classe PageTable. Elles reçoivent toutes les deux un tableau de paramètres de l'objet de requête en guise d'argument. pageAdmin: class: acClientObjectRouteCollection options: # ... model_methods: list: fetchAll object: findForRoute Modifier les Paramètres par Défaut Enfin, il arrive parfois qu'il faille rendre un paramètre spécifique disponible dans l'objet de requête pour chaque route de la collection. Toutes les collections de route bénéficient d'une option supplémentaire default_params qui permet d'y parvenir facilement. pageAdmin: class: acClientObjectRouteCollection options: # ... default_params: foo: bar Conclusion La principale responsabilité du framework de routage de symfony était à l'origine de faire correspondre URLs et de générer des URLs. Cependant, il a finalement très vite évolué vers un système entièrement personnalisable et capable de s'adapter à la plupart des besoins d'URLs complexes dans un projet. En prenant le contrôle sur les objets de route, la structure spéciale d'une URL peut ainsi être abstraite en dehors de la logique métier, et conservée entièrement à l'intérieur de la route à laquelle elle appartient. Il en résulte alors davantage de contrôle, de flexibilité et un code beaucoup plus maniable. « Introduction Accroître la Productivité » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] The More with symfony book | Techniques Avancées de Routage | symfony | Web PHP Framework http://www.symfony-project.org/more-with-symfony/1_4/fr/02-Advanced-Routing[31/12/2010 02:33:39] The More with symfony book | Accroître la Productivité | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Accroître la Productivité You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Fabien Potencier Utiliser symfony en tant que tel est, pour un développeur web, un excellent moyen d'améliorer sa productivité. En effet, tout le monde s'accorde à affirmer combien les exceptions détaillées de symfony ainsi que la barre de débogage (web debug toolbar) sont d'excellents outils qui participent à l'amélioration de la productivité. Ce chapitre a pour objectif de présenter quelques trucs et astuces afin de parfaire davantage la productivité en utilisant quelques unes des fonctionnalités bien connues de symfony, ainsi que d'autres qui le sont moins. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Automatiser le Processus de Création de Projet Grâce à l'outil en ligne de commande, symfony fournit un moyen simple, rapide et efficace pour initier un nouveau projet. $ php /path/to/symfony generate-project foo --orm=Doctrine La tâche generate:project génère la structure de base des répertoires par défaut du nouveau projet, et crée les fichiers de configuration en leur spécifiant certaines valeurs par défaut. Le framework symfony s'accompagne d'autres tâches pour créer des applications, installer des plugins, configurer le modèle de données et bien plus encore. Cependant, les toutes premières étapes de création d'un nouveau projet sont généralement toujours les mêmes : création d'une application principale, installation de quelques plugins, personnalisation de directives de configuration par défaut, etc. Depuis symfony 1.3, le processus de création d'un projet peut être entièrement personnalisé et automatisé. Comme toutes les tâches de symfony sont des classes, il est particulièrement facile de les personnaliser et de les étendre. En revanche, la tâche generate:project est plus difficilement personnalisable car il n'existe pas encore de projet lorsqu'elle est exécutée. La tâche generate:project accepte une option --installer correspondant à un chemin absolu vers un script PHP qui sera exécuté au cours du processus de création du projet. $ php /path/to/symfony generate:project --installer=/somewhere/my_installer.php Le script /somewhere/my_installer.php est exécuté dans le contexte de l'instance sfGeneratProjectTask afin de bénéficier des méthodes de la tâche en utilisant l'objet $this. Les sections suivantes décrivent toutes les méthodes disponibles pour personnaliser le processus de création d'un projet. Si l'import de fichiers grâce aux URLs est configuré dans le fichier de php.ini pour la fonction native include(), alors le chemin du script d'installation peut être remplacé par une URL. Néanmoins, il convient de rester très prudent en utilisant cette méthode, en particulier quand le contenu du script d'installation est peu, voire pas du tout, connu. $ symfony generate:project --installer=http://example.com/sf_installer.php La Méthode installDir() La méthode installDir() duplique une structure de répertoires, composée de sous-répertoires et de fichiers, dans le nouveau projet créé. http://www.symfony-project.org/more-with-symfony/1_4/fr/03-Enhance-your-Productivity[31/12/2010 02:33:45] Chapter Content Automatiser le Processus de Création de Projet La Méthode installDir() La Méthode runTask() Les Loggers Les Interactions avec l'Utilisateur Les Opérations sur le Système de Fichiers Développer plus Vite Choisir un Environnement de Développement Intégré Trouver de la Documentation Rapidement API en Ligne Les Feuilles de Triche Documentation Hors Ligne Outils en Ligne The More with symfony book | Accroître la Productivité | symfony | Web PHP Framework $this->installDir(dirname(__FILE__).'/skeleton'); La Méthode runTask() La méthode runTask() exécute une tâche. Elle prend en paramètres le nom de la tâche ainsi qu'une chaîne représentant les arguments et les options à passer à la tâche. Déboguer Plus Vite Tester plus Vite Enregistrer des Tests Fonctionnels Exécuter des Suites de Tests plus Rapidement $this->runTask('configure:author', "'Fabien Potencier'"); Les arguments et les options peuvent aussi être passés sous la forme de tableaux associatifs. $this->runTask('configure:author', array('author' => 'Fabien Potencier')); Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Les noms raccourcis d'une tâche fonctionnent également : $this->runTask('cc'); Feb 21: Paris Mar 21: Paris $this->runTask('plugin:install', 'sfDoctrineGuardPlugin'); Pour installer une version spécifique d'un plugin, il suffit simplement de lui fournir les options nécessaires. $this->runTask('plugin:install', 'sfDoctrineGuardPlugin', array('release' => '10.0.0', 'stability' => beta')); L'exécution d'une tâche comprise dans un plugin nouvellement installé est possible à condition de recharger la liste des tâches avant de l'utiliser. $this->reloadTasks(); Il existe des tâches qui dépendent d'une application spécifique pour être exécuter. C'est le cas par exemple de la tâche generate:module. Par conséquent, il est nécessaire de changer le contexte de la configuration pour pouvoir l'utiliser. $this->setConfiguration($this->createConfiguration('frontend', 'dev')); Les Loggers Afin de fournir le maximum d'informations au développeur sur l'exécution du script d'installation, certaines informations peuvent être enregistrées en cours d'exécution. // a simple log $this->log('some installation message'); // log a block $this->logBlock(array('', 'Fabien\'s Crazy Installer', ''), 'ERROR'); // log in a section $this->logSection('install', 'install some crazy files'); Les Interactions avec l'Utilisateur Les méthodes askConfirmation(), askAndValidate() et ask() offrent un moyen de rendre le processus d'installation configurable et dynamique par l'intermédiaire de questions posées à l'utilisateur dans la console. Par exemple, si la question nécessite une confirmation en guise de réponse, alors il suffit d'utiliser la méthode askConfirmation() comme le montre le code ci-dessous. if (!$this->askConfirmation('Are you sure you want to run this crazy installer?')) { $this->logSection('install', 'You made the right choice!'); return; } De la même manière, il est possible de poser n'importe quelle question à l'utilisateur, et obtenir sa saisie en guise de réponse grâce à la méthode ask(). Cette méthode retourne la réponse de l'utilisateur sous forme d'une chaîne de caractères. http://www.symfony-project.org/more-with-symfony/1_4/fr/03-Enhance-your-Productivity[31/12/2010 02:33:45] (Maîtrise de & Doctrine - Français) Apr 18: Paris Cette méthode peut bien sûr être utilisée pour installer des plugins. (Maîtrise de & Doctrine - Français) (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Search powered by google The More with symfony book | Accroître la Productivité | symfony | Web PHP Framework $secret = $this->ask('Give a unique string for the CSRF secret:'); Enfin, la méthode askAndValidate() est, quant à elle, responsable de la récupération de la réponse de l'utilisateur, et de sa validation à la volée à l'aide d'un validateur. $validator = new sfValidatorEmail(array(), array('invalid' => 'hmmm, it does not look like an email!')); $email = $this->askAndValidate('Please, give me your email:', $validator); Les Opérations sur le Système de Fichiers Le script d'installation est également capable de réaliser des modifications sur le système de fichiers en accédant à l'objet sfFilesystem de symfony. $this->getFilesystem()->...(); Le Processus de Création de la Sandbox Le bac à sable de symfony est un projet préfabriqué, incluant une application prête à l'emploi et une base de données SQLite préconfigurée. Son processus de création est réalisé à partir d'un script d'installation que quiconque peut utiliser pour générer sa propre sandbox comme l'illustre le code ci-dessous. $ php symfony generate:project -installer=/path/to/symfony/data/bin/sandbox_installer.php Etudier le contenu du fichier symfony/data/bin/sandbox_installer.php permet d'avoir un bon exemple du fonctionnement des scripts d'installation. Le script d'installation est un pur fichier PHP. Par conséquent, il peut réaliser à peu près tout ce dont désire le développeur. Les scripts d'installation sont un moyen sûr et rapide de personnaliser et d'industrialiser la création de projets symfony, tout en protégeant le développeur des étapes manquantes. Ces fichiers peuvent également être partagés avec les autres membres de la communauté symfony. Développer plus Vite Du code PHP aux tâches en ligne de commande, programmer signifie aussi taper souvent sur son clavier. La suite de ce chapitre présente comment symfony aide le développeur à réduire cette tâche à son plus strict minimum. Choisir un Environnement de Développement Intégré Utiliser un EDI (IDE pour l'acronyme anglais) aide le développeur à être toujours plus productif dans différents domaines. Pour commencer, il faut savoir que tous les EDIs modernes fournissent par défaut un mécanisme d'autocomplétion du code PHP qui facilite l'écriture de code lorsque l'on ne connait pas le nom complet d'une méthode recherchée par exemple. Ainsi, il n'est plus nécessaire de parcourir la documentation de l'API dans la mesure où l'EDI est capable de suggérer toutes les méthodes disponibles pour l'objet courant. Enfin, il faut savoir que certains EDIs comme PHPEdit ou bien Netbeans en savent beaucoup plus sur symfony car ils fournissent une intégration spécifique et plus poussée des projets symfony. Les Editeurs de Texte Certains utilisateurs préfèrent utiliser un éditeur de texte pour développer, principalement parce que les éditeurs de texte sont plus rapides que n'importe quel autre EDI. Cependant, les éditeurs de texte offrent bien moins de fonctionnalités par rapport aux EDIs. La plupart des éditeurs offrent toutefois des plugins / extensions qui peuvent être utilisés pour améliorer l'expérience utilisateur et rendre le logiciel plus efficace avec PHP et les projets symfony. Par exemple, de nombreux utilisateurs de Linux tendent à utiliser VIM pour toutes leurs tâches de travail. Pour ces développeurs, une extension est disponible : vim-symfony. VIM-symfony est un jeu de scripts qui intègrent symfony dans l'éditeur. En utilisant vim-symfony, il est ainsi possible de créer facilement des macros et des commandes vim pour rationaliser les développements symfony. Cet outil embarque de plus un jeu de commandes par défaut qui mettent un certain nombre de fichiers de configuration à portée de main du développeur (schéma, routage, etc) ainsi qu'une manière de passer simplement des actions aux vues http://www.symfony-project.org/more-with-symfony/1_4/fr/03-Enhance-your-Productivity[31/12/2010 02:33:45] The More with symfony book | Accroître la Productivité | symfony | Web PHP Framework (templates). Certains développeurs Mac OS X préfèrent utiliser TextMate et peuvent donc ainsi installer le bundle symfony, qui offre un certain nombre de macros et de raccourcis qui aident économiser du temps au quotidien. Utiliser un EDI qui supporte symfony Certains EDIs comme PHPEdit 3.4 et NetBeans 6.8 disposent d'un support natif de symfony, et fournissent ainsi une intégration très pointue du framework. Il suffit de parcourir leur documentation respective pour en savoir davantage à propos de leur support spécifique de symfony et ainsi déterminer dans quelle mesure ils participent à l'amélioration de la productivité. Aider l'EDI L'autocomplétion de PHP dans les EDIs fonctionne uniquement pour les méthodes qui sont explicitement déclarées dans le code PHP. En revanche, si le code fait usage de méthodes "magiques" __call() ou __get() par exemple, les EDIs n'auront aucun moyen de deviner les méthodes et propriétés disponibles. Heureusement, la bonne nouvelle c'est que le développeur a la capacité d'aider l'EDI en lui fournissant des informations sur les méthodes et propriétés du fichier à l'aide de blocs de PHPDoc. Il suffit pour cela d'utiliser respectivement les annotations @method et @property. Le code ci-dessous fait état d'une classe Message dans laquelle se trouvent une propriété (message) et une méthode (getMessage()) dynamiques. Il montre comment l'EDI peut avoir connaissance de ces propriété et méthode, bien qu'il n'y ait aucune définition explicite dans le code PHP. /** * @property clob $message * * @method clob getMessage() Returns the current message value */ class Message { public function __get() { // ... } public function __call() { // ... } } Même si la méthode getMessage() n'existe pas, elle sera quand même reconnue par l'EDI grâce à l'annotation @method. Il en va de même pour la propriété message du fait de la présence de l'annotation @property. Cette technique est notamment utilisée par la tâche doctrine:build-model. Par exemple, une classe Doctrine MailMessage avec deux colonnes (message et priority) ressemblera au code ci-dessous. /** * BaseMailMessage * * This class has been auto-generated by the Doctrine ORM Framework * * @property clob $message * @property integer $priority * * @method clob getMessage() Returns the current record's "message" value * @method integer getPriority() Returns the current record's "priority" value * @method MailMessage setMessage() Sets the current record's "message" value * @method MailMessage setPriority() Sets the current record's "priority" value * * @package ##PACKAGE## * @subpackage ##SUBPACKAGE## * @author ##NAME## <##EMAIL##> * @version SVN: $Id: Builder.php 6508 2009-10-14 06:28:49Z jwage $ http://www.symfony-project.org/more-with-symfony/1_4/fr/03-Enhance-your-Productivity[31/12/2010 02:33:45] The More with symfony book | Accroître la Productivité | symfony | Web PHP Framework */ abstract class BaseMailMessage extends sfDoctrineRecord { public function setTableDefinition() { $this->setTableName('mail_message'); $this->hasColumn('message', 'clob', null, array( 'type' => 'clob', 'notnull' => true, )); $this->hasColumn('priority', 'integer', null, array( 'type' => 'integer', )); } public function setUp() { parent::setUp(); $timestampable0 = new Doctrine_Template_Timestampable(); $this->actAs($timestampable0); } } Trouver de la Documentation Rapidement Symfony est un framework riche en fonctionnalités, et de ce fait, il n'est pas toujours évident de se rappeler toutes les possibilités de configuration, ou bien toutes les classes et les méthodes mise à disposition. La précédente section a montré qu'utiliser un EDI est un moyen efficace pour profiter de l'autocomplétion. Il est désormais temps de découvrir les autres outils existant qui peuvent être employés pour trouver des réponses aussi vite que possible. API en Ligne La manière la plus rapide de trouver de la documentation à propos d'une classe ou d'une méthode de symfony consiste à parcourir l'API en ligne. Le moteur de recherche intégré à l'API en ligne est encore plus intéressant. La recherche permet de trouver rapidement une classe ou bien une méthode avec seulement quelques frappes de clavier. Après seulement quelques lettres saisies dans la boîte de recherche, une boîte de résultats apparaîtra aussi vite avec davantage de suggestions très pratiques. La recherche s'effectue en tapant le début d'un nom de classe. Ou bien en saisissant le nom de méthode. Ou encore en tapant le nom d'une classe suivi par :: afin de lister toutes les méthodes disponibles. Ou le début d'un nom de méthode afin d'affiner les possibilités. http://www.symfony-project.org/more-with-symfony/1_4/fr/03-Enhance-your-Productivity[31/12/2010 02:33:45] The More with symfony book | Accroître la Productivité | symfony | Web PHP Framework Si l'on souhaite lister toutes les classes d'un paquetage, il suffit de saisir le nom du paquet, puis de soumettre la recherche. Il est également possible d'intégrer l'API de recherche de symfony dans le navigateur du client. De cette manière, il n'est plus nécessaire de naviguer sur le site de symfony lorsqu'il s'agit de rechercher une information. C'est en effet possible puisque l'API en ligne de symfony bénéficie de la recherche native OpenSearch. Pour les utilisateurs de Firefox, le moteur de recherche de l'API en ligne de symfony apparaîtra automatiquement dans la barre des moteurs de recherches du navigateur. Un clic sur le lien "API OpenSearch" depuis la documentation de l'API en ligne permet de l'ajouter à la boîte de recherche du navigateur. Un billet a été rédigé sur le blog de symfony qui présente comment le moteur de recherche de l'API s'intègre à Firefox à l'aide d'un screencast. Les Feuilles de Triche Il existe aujourd'hui une vaste collection de feuilles de triche (cheat sheets) qui permettent d'accéder directement aux informations des majeures parties du framework : Structure des Répertoires et CLI La Vue La Vue: Partials, Components, Slots et Component Slots Tests Unitaires et Fonctionnels avec Lime ORM Propel Propel Schema Doctrine Certaines de ces feuilles de triche n'ont pas encore été mises à jour pour >symfony 1.3. Documentation Hors Ligne Les réponses aux questions de configuration se trouvent principalement dans le guide de référence de symfony. C'est le livre que tout développeur symfony se doit de garder près de lui car il est le moyen le plus rapide de trouver n'importe quelle configuration disponible à partir d'une table des matières détaillée, un index des mots-clés et des références croisées à l'intérieur des chapitres, tableaux et plus encore. Le livre est consultable gratuitement en ligne, disponible à l'achat pour une copie papier ou bien encore de le télécharger gratuitement en version PDF. Outils en Ligne Le début de ce chapitre a montré que symfony fournit un large éventail d'outils pour aider le développeur à démarrer rapidement. Arrivé au terme de son développement, un projet symfony doit alors être déployé sur le serveur de production. Pour s'assurer qu'un projet est prêt au déploiement, la checklist en ligne du déploiement couvre les principaux points importants à vérifier avant de basculer l'application en production. Déboguer Plus Vite Lorsqu'une erreur se produit en environnement de développement, symfony affiche une page d'exception explicite contenant de nombreuses informations utiles. Il est possible, par exemple, d'analyser la trace de la pile d'appels des méthodes (et fonctions) et des fichiers qui ont été exécutés. De plus, en définissant le paramètre sf_file_link_format du fichier de configuration settings.yml (voir ci-dessous), les noms des fichiers deviennent automatiquement cliquables dans la trace de débogage de symfony. Ces derniers s'ouvriront ensuite dans l'éditeur ou EDI http://www.symfony-project.org/more-with-symfony/1_4/fr/03-Enhance-your-Productivity[31/12/2010 02:33:45] The More with symfony book | Accroître la Productivité | symfony | Web PHP Framework configuré, et le curseur sera automatiquement positionné à la ligne où l'erreur a été générée. C'est un excellent exemple d'une toute petite fonctionnalité qui peut faire économiser un temps précieux aux développeurs lorsqu'ils font face à un problème. Les panneaux dédiés à la vue et aux logs de la barre de débogage affichent eux aussi les noms de fichiers (particulièrement lorsque l'extension xDebug est activée) qui deviennent cliquables lorsque le paramètre sf_file_link_format est défini. Par défaut, le paramètre sf_file_link_format n'est pas configuré et symfony utilise la valeur de la directive de configuration xdebug.file_link_format si elle existe. Le paramètre xdebug.file_link_format du php.ini permet à des versions récentes de xDebug d'ajouter des liens pour tous les noms de fichiers présents dans la pile des appels. La valeur du paramètre sf_file_link_format dépend à la fois de l'EDI et du système d'exploitation. Par exemple, pour les utilisateurs de TextMate, il suffit d'ajouter la configuration suivante dans le fichier settings.yml: dev: .settings: file_link_format: txmt://open?url=file://%f&line=%l Le jeton %f est remplacé par symfony par le chemin absolu vers le fichier tandis que la chaîne %l est remplacée par le numéro de la ligne concernée. Pour les utilisateurs de VIM, la configuration est un peu plus évoluée et décrite en ligne pour symfony et XDebug. N'hésitez pas à utiliser votre moteur de recherche favoris afin d'apprendre comment configurer votre EDI. Vous pouvez ainsi rechercher la configuration des paramètres sf_file_link_format et sf_file_link_format dans la mesure où ils fonctionnent de la même manière. Tester plus Vite Enregistrer des Tests Fonctionnels Les tests fonctionnels simulent les interactions de l'utilisateur afin de tester globalement l'intégration de toutes les parties de l'application entre elles. Ecrire des tests fonctionnels est particulièrement simple mais aussi chronophage. Néanmoins, comme chaque fichier de tests fonctionnels est un scénario qui simule un utilisateur naviguant sur le site, et parce que naviguer réellement sur une application est plus rapide que d'écrire du code PHP, pourquoi ne pas enregistrer directement le scénario en naviguant sur l'application avant de le convertir à la volée en code PHP ? Heureusement, symfony dispose d'un plugin pour remplir ce besoin. Il s'agit du plugin swFunctionalTestGenerationPlugin qui permet de générer des squelettes de tests prêts à être personnalisés en quelques minutes. Il est bien évidemment du rôle du développeur d'ajouter ses propres appels aux différents testeurs afin de rendre le test utile et pertinent ; mais ce plugin reste cependant un excellent moyen d'économiser du temps. Le greffon swFunctionalTestGenerationPlugin fonctionne par l'intermédiaire d'un filtre symfony qui intercepte toutes les requêtes qu'il convertit à la volée en code de tests fonctionnels. Après avoir installé le plugin de manière traditionnelle, il est nécessaire de l'activer en ajoutant les lignes suivantes après la ligne commentée du fichier filters.yml : functional_test: class: swFilterFunctionalTest Ensuite, le plugin doit être activé dans la classe ProjectConfiguration du projet : // config/ProjectConfiguration.class.php class ProjectConfiguration extends sfProjectConfiguration { public function setup() { // ... $this->enablePlugin('swFunctionalTestGenerationPlugin'); } } http://www.symfony-project.org/more-with-symfony/1_4/fr/03-Enhance-your-Productivity[31/12/2010 02:33:45] The More with symfony book | Accroître la Productivité | symfony | Web PHP Framework Comme le plugin utilise la barre de débogage en guise d'interface utilisateur principale, il faut alors s'assurer qu'elle est bien activée (ce qui est déjà le cas par défaut en environnement de test). Une fois activée, un nouveau menu intitulé "Functional Test" apparaît dans la barre de débogage. Ce nouveau panneau permet entre autres de démarrer l'enregistrement d'une session en cliquant sur le lien "Activate" ou bien de réinitialiser la session courante en cliquant sur "Reset". A la fin de l'enregistrement, il ne reste plus qu'à copier et coller le code du champ multiligne dans un fichier de tests fonctionnels, juste avant de démarrer sa personnalisation. Exécuter des Suites de Tests plus Rapidement Lorsque le projet contient une longue suite de tests, cette dernière peut s'avérer particulièrement chronophage si les tests sont exécutés après chaque changement. Cela est d'autant plus vrai quand seulement quelques tests échouent parmi un large jeu de tests. A chaque fois que le développeur corrige un test, une nouvelle exécution de toute la suite de tests devrait être lancée pour s'assurer que les autres tests n'ont pas été cassés. Or, tant que les tests échoués ne sont pas tous fixés, il n'existe pas nécessairement de raison de relancer tous les autres tests. L'objectif consiste à accélérer ce processus. La tâche test:all inclut désormais une nouvelle option --only-failed (raccourci -f) qui force la tâche à ne relancer que les tests qui ont échoué à l'exécution précédente. $ php symfony test:all --only-failed A la première exécution, tous les tests sont exécutés tandis que les suivantes n'exécuteront que ceux qui ont échoué au passage précédent. Une fois le code et les tests corrigés, ces derniers passeront donc puis seront supprimés des les exécutions suivantes. Lorsque tous les tests passent à nouveau, la suite de tests complète est exécutée... « Techniques Avancées de Routage Les Emails » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/03-Enhance-your-Productivity[31/12/2010 02:33:45] The More with symfony book | Les Emails | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Les Emails You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Fabien Potencier Envoyer des emails avec symfony devient à la fois simple et plus efficace, grâce à l'utilisation de la bibliothèque Swift Mailer. Bien que Swift Mailer facilite l'envoi des emails, symfony apporte quant à lui une couche supplémentaire afin de rendre l'envoi d'emails encore plus flexible et puissant. Ce chapitre explique comment les développeurs peuvent tirer parti de toute cette puissance. symfony 1.3 embarque la version 4.1 de Swift Mailer. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Introduction La gestion des emails dans symfony est centralisée autour d'un objet de gestion d'envoi d'email, le mailer. Comme pour la plupart des autres objets qui composent le coeur de symfony, l'objet mailer est lui aussi une factory. De ce fait, il est configuré par l'intermédiaire du fichier de configuration factories.yml, et toujours exposé à travers l'instance du contexte. $mailer = sfContext::getInstance()->getMailer(); Contrairement aux autres factories, le gestionnaire d'envoi d'emails est chargé et initialisé à la demande. Par conséquent, s'il n'est pas utilisé, il n'y aura aucun impact sur les performances. Ce tutoriel explique comment est intégrée la librairie Swift Mailer dans symfony. Les lecteurs qui souhaitent en savoir davantage sur les détails importants de Swift Mailer sont invités à se référer à la documentation officielle en ligne. Envoyer des Emails depuis une Action Depuis une action, la récupération du gestionnaire d'envoi d'emails a été facilitée grâce à la méthode raccourcie getMailer() : $mailer = $this->getMailer(); La Méthode Rapide Envoyer un email est alors aussi simple que d'utiliser la méthode sfAction::composeAndSend() : $this->getMailer()->composeAndSend( '[email protected]', '[email protected]', 'Subject', 'Body' ); La méthode composeAndSend() accepte quatre arguments : L'adresse email de l'expéditeur (from) ; La / les adresse(s) email du / des destinataire(s) (to) ; Le sujet du message ; Le corps du message. Toutes les méthodes qui accueillent une adresse e-mail en guise de paramètre peuvent en fait accepter aussi bien une chaîne de caractères comme un tableau. http://www.symfony-project.org/more-with-symfony/1_4/fr/04-Emails[31/12/2010 02:33:48] Chapter Content Introduction Envoyer des Emails depuis une Action La Méthode Rapide La Méthode Flexible La Méthode Efficace Coupler l'Envoi d'Emails avec la Vue de symfony La Configuration La Stratégie de Distribution La Stratégie realtime La Stratégie single_address La Stratégie spool La Stratégie none Le Transport des Emails Envoyer un Email depuis une Tâche The More with symfony book | Les Emails | symfony | Web PHP Framework $address = '[email protected]'; $address = array('[email protected]' => 'Fabien Potencier'); Bien sûr, le tableau peut contenir plusieurs adresses email afin d'expédier le message à plusieurs destinataires simultanément. $to = array( '[email protected]', '[email protected]', ); $this->getMailer()->composeAndSend('[email protected]', $to, 'Subject', 'Body'); $to = array( '[email protected]' => 'Mr Foo', '[email protected]' => 'Miss Bar', ); $this->getMailer()->composeAndSend('[email protected]', $to, 'Subject', 'Body'); Le Débogage Tester les Emails Les Messages Electroniques sous forme de Classes Quelques Recettes Envoyer des Emails avec Gmail Personnaliser l'Objet Mailer Utiliser des Plugins Swift Mailer~ Personnaliser le Comportement de Spool Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) La Méthode Flexible Bien qu'elle soit simple et rapide à mettre en place, la première méthode peut s'avérer moins flexible. La méthode sfAction::compose() accroît la flexibilité du développeur car elle permet de créer le message, de le personnaliser à volonté et éventuellement de l'envoyer. C'est d'autant plus pratique lorsqu'il s'agit d'ajouter une pièce jointe au message. Si vous avez besoin de plus de flexibilité, vous pouvez aussi utiliser la méthode sfAction::compose() pour créer un message, le personnaliser de la manière que vous voulez, et éventuellement l'envoyer. C'est, par exemple, très pratique lorsque vous avez besoin d'ajouter une pièce jointe (attachment|email attachment) au message comme le montre l'exemple ci-dessous. // create a message object $message = $this->getMailer() ->compose('[email protected]', '[email protected]', 'Subject', 'Body') ->attach(Swift_Attachment::fromPath('/path/to/a/file.zip')) ; // send the message $this->getMailer()->send($message); La Méthode Efficace Une autre méthode consiste à créer l'objet du message à la main directement afin de bénéficier d'encore plus de flexibilité. Le code ci-dessous témoigne de cette flexibilité accrue. $message = Swift_Message::newInstance() ->setFrom('[email protected]') ->setTo('[email protected]') ->setSubject('Subject') ->setBody('Body') ->attach(Swift_Attachment::fromPath('/path/to/a/file.zip')) ; $this->getMailer()->send($message); Les sections "Creating Messages" et "Message Headers" de la documentation officielle de Swift Mailer décrivent tout ce dont il faut savoir à propos de la création de messages. Coupler l'Envoi d'Emails avec la Vue de symfony Envoyer vos emails depuis les actions permet de profiter en toute aisance de la puissance des vues partielles et des composants pour assigner un contenu au message. $message->setBody($this->getPartial('partial_name', $arguments)); La Configuration Le gestionnaire d'envoi d'emails peut être configuré dans le fichier de configuration factories.yml de la même manière que toute autre factory de symfony. La configuration par défaut du gestionnaire est la suivante. http://www.symfony-project.org/more-with-symfony/1_4/fr/04-Emails[31/12/2010 02:33:48] Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Search powered by google The More with symfony book | Les Emails | symfony | Web PHP Framework mailer: class: sfMailer param: logging: %SF_LOGGING_ENABLED% charset: %SF_CHARSET% delivery_strategy: realtime transport: class: Swift_SmtpTransport param: host: localhost port: 25 encryption: ~ username: ~ password: ~ A la création d'une nouvelle application, le fichier de configuration local factories.yml surcharge la configuration par défaut, en spécifiant quelques ajustements spécifiques aux environnements de production (prod), de développement (dev) et de test (test). test: mailer: param: delivery_strategy: none dev: mailer: param: delivery_strategy: none La Stratégie de Distribution L'une des principales fonctionnalités utiles de l'intégration de Swift Mailer dans symfony est la stratégie de distribution des emails. La stratégie de distribution permet d'indiquer à symfony de quelle manière le framework doit envoyer les emails. Elle est configurée à partir du paramètre de configuration delivery_strategy du fichier factories.yml. La stratégie change la manière dont la méthode send()|sfMailer::send() se comporte. Quatre stratégies de distribution sont disponibles par défaut, ce qui devrait convenir à la majorité des besoins : realtime : les messages sont envoyés en temps réel ; single_address : les messages sont envoyés à une adresse unique ; spool : les messages sont stockés dans une file d'attente ; none : les messages sont simplement ignorés. La Stratégie realtime La stratégie realtime est la stratégie de distribution par défaut car c'est aussi la plus facile à configurer dans la mesure où il n'y a finalement rien de spécial à faire. Les emails sont expédiés à l'aide d'un objet de transport configuré dans la section transport du fichier de configuration factories.yml. La prochaine section donne davantage d'informations à propos de la configuration de l'objet de transport. La Stratégie single_address Avec la stratégie single_address, tous les messages sont envoyés à une unique adresse email. Cette stratégie est configurée au paramètre de configuration delivery_strategy. La stratégie single_address est particulièrement utile en environnement de développement afin d'éviter d'envoyer des emails aux utilisateurs finaux réels. Le développeur garde néanmoins une grande flexibilité dans la mesure où il peut toujours consulter le rendu du message dans un client mail. Le développeur peut avoir besoin de vérifier les valeurs des destinataires originaux dans les en-têtes to, cc et bcc. Ces valeurs sont disponibles dans les entêtes respectives suivantes : X-Swift-To, X-Swift-Cc et X-Swift-Bcc. Les emails sont expédiés avec le même transport d'email que celui utilisé pour la stratégie de distribution realtime. http://www.symfony-project.org/more-with-symfony/1_4/fr/04-Emails[31/12/2010 02:33:48] The More with symfony book | Les Emails | symfony | Web PHP Framework La Stratégie spool Avec la stratégie de spool, les messages sont sauvegardés dans une file d'attente. C'est sans aucun doute la meilleure stratégie pour l'environnement de production dans la mesure où les requêtes web n'ont pas à attendre que les emails ont bien été envoyés. La classe de spool est configurée dans le paramètre de configuration spool_class du fichier factories.yml, et symfony inclut trois de ces stratégies par défaut : Swift_FileSpool : les messages sont stockés sur le système de fichiers ; Swift_DoctrineSpool : les messages sont stockés dans un modèle Doctrine ; Swift_PropelSpool : les messages sont stockés dans un modèle Propel. Lorsque la classe de spool est instanciée, les valeurs définies dans le paramètre de configuration spool_arguments sont utilisées comme arguments du constructeur. Les options de configuration disponibles pour les classes de file d'attente natives sont listées ci-dessous : Swift_FileSpool : Le chemin absolu du répertoire de la file d'attente (les messages sont stockés dans ce répertoire). Swift_DoctrineSpool : Le modèle Doctrine à utiliser pour sauvegarder les messages (MailMessage par défaut). Le nom de la colonne à utiliser pour le stockage du message (message par défaut). La méthode à appeler pour retrouver le message à envoyer (optionnel). Elle reçoit les options de la file d'attente comme argument. Swift_PropelSpool : Le modèle Propel à utiliser pour sauvegarder les messages (MailMessage par défaut). Le nom de la colonne à utiliser pour le stockage du message (message par défaut). La méthode à appeler pour retrouver le message à envoyer (optionnel). Elle reçoit les options de la file d'attente comme argument. Le listing ci-dessous décrit une configuration typique du spool Doctrine : # Schema configuration in schema.yml MailMessage: actAs: { Timestampable: ~ } columns: message: { type: blob, notnull: true } # configuration in factories.yml mailer: class: sfMailer param: delivery_strategy: spool spool_class: Swift_DoctrineSpool spool_arguments: [ MailMessage, message, getSpooledMessages ] Le code ci-après décrit la même configuration pour le spool Propel : # Schema configuration in schema.yml mail_message: message: { type: blob, required: true } created_at: ~ # configuration in factories.yml dev: mailer: param: delivery_strategy: spool spool_class: Swift_PropelSpool spool_arguments: [ MailMessage, message, getSpooledMessages ] Pour envoyer un message sauvegardé dans la file d'attente, il suffit d'utiliser la tâche project:send-emails. Il est important de noter que cette commande est complètement indépendante de l'implémentation de la file d'attente, et des options qu'elle accepte. $ php symfony project:send-emails http://www.symfony-project.org/more-with-symfony/1_4/fr/04-Emails[31/12/2010 02:33:48] The More with symfony book | Les Emails | symfony | Web PHP Framework La tâche project:send-emails accepte aussi les options application et env. Lorsque la tâche project:send-emails est invoquée, les emails sont envoyés à l'aide du même objet de transport que celui défini pour la stratégie realtime. La tâche project:send-emails est exécutable sur n'importe quelle machine, et pas nécessairement sur la machine qui a créé le message. Cela fonctionne en effet parce que tout est sauvegardé dans l'objet du message, y compris les fichiers attachés. Les implémentations des files d'attente par défaut sont particulièrement triviales. Elles envoient les emails sans aucune gestion d'erreur, comme si elles avaient été envoyées avec la stratégie realtime. Bien sûr, les classes de files d'attente par défaut peuvent être étendues afin d'implémenter une logique métier et une gestion des erreurs personnalisées. Il arrive parfois qu'il faille envoyer un message immédiatement sans avoir à le sauvegarder dans la file d'attente, bien que l'application soit configurée avec la stratégie de spool. Heureusement, symfony fournit la méthode spéciale sendNextImmediately() de l'objet mailer pour satisfaire ce besoin. $this->getMailer()->sendNextImmediately()->send($message); Dans l'exemple précédent, l'objet $message ne sera pas sauvegardé dans la file d'attente et sera immédiatement expédié. Comme son nom l'indique, la méthode sendNextImmediately() affecte seulement le tout prochain message à être envoyé. La méthode sendNextImmediately() n'a aucun effet particulier lorsque la stratégie de distribution n'est pas définie à la valeur spool. La Stratégie none La stratégie none est utile en environnement de développement dans la mesure où elle empêche tout email d'être envoyé aux destinataires finaux. Néanmoins, les messages restent disponibles dans la barre de débogage. La section suivante donne davantage d'informations au sujet du panneau de gestion des emails de la barre d'outils. Cette stratégie est également la meilleure pour l'environnement de test. En effet, le testeur stTesterMailer offre la possibilité d'introspecter les messages sans avoir le besoin de les envoyer réellement. Les tests sur les messages envoyés sont décrits dans la section suivante. Le Transport des Emails Les emails sont actuellement expédiés à l'aide d'un objet de transport. Le transport est configuré dans le fichier de configuration factories.yml, et sa configuration par défaut force une connexion au serveur SMTP de la machine locale : transport: class: Swift_SmtpTransport param: host: localhost port: 25 encryption: ~ username: ~ password: ~ Swift Mailer embarque nativement trois classes de transport différentes : Swift_SmtpTransport utilise un serveur SMTP pour envoyer les messages ; Swift_SendmailTransport utilise le binaire sendmail pour envoyer les messages ; Swift_MailTransport utilise la fonction native mail() de PHP pour envoyer les emails. La section "Transport Types" de la documentation officielle de Swift Mailer décrit tout ce dont il faut savoir à propos des classes de transport natives et leurs différents paramètres. Envoyer un Email depuis une Tâche http://www.symfony-project.org/more-with-symfony/1_4/fr/04-Emails[31/12/2010 02:33:48] The More with symfony book | Les Emails | symfony | Web PHP Framework Envoyer un email depuis une tâche est pratiquement similaire à envoyer un email depuis une action dans la mesure où le mécanisme des tâches expose une méthode getMailer(). Le système de tâches dépend de la configuration courante au moment de la création de l'objet mailer. Par conséquent, si la tâche a besoin de la configuration d'une application spécifique, alors la commande doit obligatoirement accepter l'option --application. Le chapitre sur les tâches donne davantage d'explications à ce sujet. Il est important de remarquer que la tâche utilise la même configuration que les contrôleurs. Par conséquent, pour forcer la distribution du message, bien que ce soit la stratégie de spool qui soit utilisée, alors il suffit d'utiliser la méthode sendNextImmediately() : $this->getMailer()->sendNextImmediately()->send($message); Le Débogage Depuis toujours, le débogage des emails a toujours été un véritable cauchemar. Avec symfony, c'est beaucoup plus simple grâce au nouveau panneau email de la web debug toolbar. Avec tout le confort du navigateur web, il est désormais possible de savoir facilement et rapidement combien de messages ont été expédiés dans l'action courante. Un clic sur l'icône des emails donne accès à tous les messages envoyés, affichés sous forme brute comme l'atteste la capture d'écran ci-dessous. Chaque fois qu'un email est envoyé, symfony ajoute au passage un message dans le log. Tester les Emails Bien sûr, l'intégration des emails n'aurait pas été aussi complète sans un moyen de tester les messages. Par défaut, symfony enregistre un nouveau testeur mailer (sfMailerTester) afin de faciliter les tests fonctionnels sur les emails envoyés. La méthode hasSent(), par exemple, teste le nombre de messages envoyés au cours de la requête courante. $browser-> get('/foo')-> with('mailer')-> hasSent(1) ; Le code précédent vérifie que l'url /foo envoie seulement un email. De plus, les spécificités de chaque email envoyé peuvent être testées à l'aide des méthodes checkHeader() et checkBody(). $browser-> get('/foo')-> with('mailer')->begin()-> hasSent(1)-> checkHeader('Subject', '/Subject/')-> http://www.symfony-project.org/more-with-symfony/1_4/fr/04-Emails[31/12/2010 02:33:48] The More with symfony book | Les Emails | symfony | Web PHP Framework checkBody('/Body/')-> end() ; Le second argument de la méthode checkHeader() et le premier paramètre de checkBody() peuvent être l'une des valeurs suivantes. une chaîne pour vérifier une correspondance exacte ; une expression régulière pour contrôler la correspondance de la valeur avec elle ; une expression régulière négative (une expression régulière qui débute par un !) pour vérifier que la valeur ne correspond pas. Par défaut, les vérifications sont réalisées sur le premier message envoyé. Si plusieurs messages ont été expédiés, la méthode withMessage() offre la possibilité de choisir sur quel message appliquer les tests. $browser-> get('/foo')-> with('mailer')->begin()-> hasSent(2)-> withMessage('[email protected]')-> checkHeader('Subject', '/Subject/')-> checkBody('/Body/')-> end() ; La méthode withMessage() accepte une adresse email de destinataire en guise de premier argument. Elle accueille également un second paramètre pour indiquer quel message tester si plusieurs emails ont été adressés à la même personne. Enfin, la méthode debug() expose les messages envoyés afin de déceler les problèmes lorsqu'un test échoue. $browser-> get('/foo')-> with('mailer')-> debug() ; Les Messages Electroniques sous forme de Classes L'introduction de ce chapitre a montré comment envoyer des emails depuis une action. C'est sans doute la manière la plus simple pour expédier des messages dans une application symfony, et probablement la meilleure lorsqu'il s'agit seulement d'envoyer quelques emails simples. Néanmoins, plus l'application a besoin de gérer un nombre important de messages, et plus le risque d'adopter une stratégie différente augmente. Comme tous les messages sont des objets PHP purs, la manière évidente d'organiser les messages consiste à créer une classe pour chacun d'eux. // lib/email/ProjectConfirmationMessage.class.php class ProjectConfirmationMessage extends Swift_Message { public function __construct() { parent::__construct('Subject', 'Body'); $this ->setFrom(array('[email protected]' => 'My App Bot')) ->attach('...') ; } } Envoyer un message depuis une action, ou bien depuis n'importe où dans ce cas est aussi simple que d'instancier la classe du message correspondant. $this->getMailer()->send(new ProjectConfirmationMessage()); Bien sûr, il est plus pratique d'ajouter une classe de base pour centraliser les en-têtes partagés tels que l'en-tête From, ou bien pour inclure une signature commune. // lib/email/ProjectConfirmationMessage.class.php class ProjectConfirmationMessage extends ProjectBaseMessage http://www.symfony-project.org/more-with-symfony/1_4/fr/04-Emails[31/12/2010 02:33:48] The More with symfony book | Les Emails | symfony | Web PHP Framework { public function __construct() { parent::__construct('Subject', 'Body'); // specific headers, attachments, ... $this->attach('...'); } } // lib/email/ProjectBaseMessage.class.php class ProjectBaseMessage extends Swift_Message { public function __construct($subject, $body) { $body .= <<<EOF -Email sent by My App Bot EOF ; parent::__construct($subject, $body); // set all shared headers $this->setFrom(array('[email protected]' => 'My App Bot')); } } Si un message dépend de certains objets du modèle, ce dernier peut alors bien entendu être transmis comme paramètre du constructeur. // lib/email/ProjectConfirmationMessage.class.php class ProjectConfirmationMessage extends ProjectBaseMessage { public function __construct($user) { parent::__construct('Confirmation for '.$user->getName(), 'Body'); } } Quelques Recettes Envoyer des Emails avec Gmail Les lecteurs qui ne possèdent pas de serveur SMTP mais qui disposent d'un compte Gmail peuvent s'appuyer sur la configuration suivante afin d'utiliser les serveurs de Google comme moyen d'expédition et d'archivage des messages. transport: class: Swift_SmtpTransport param: host: smtp.gmail.com port: 465 encryption: ssl username: your_gmail_username_goes_here password: your_gmail_password_goes_here Remplacer les valeurs des paramètres username et password par celles du compte Gmail adéquat suffisent à configurer l'objet mailer. Personnaliser l'Objet Mailer Si configurer le mailer par le fichier factories.yml n'est pas suffisant, l'évènement mailer.configure peut alors être écouté afin de personnaliser davantage le mailer. Pour ce faire, il suffit de se connecter à l'évènement depuis la classe de configuration ProjectConfiguration comme le montre l'exemple ci-dessous. class ProjectConfiguration extends sfProjectConfiguration { public function setup() { // ... http://www.symfony-project.org/more-with-symfony/1_4/fr/04-Emails[31/12/2010 02:33:48] The More with symfony book | Les Emails | symfony | Web PHP Framework $this->dispatcher->connect( 'mailer.configure', array($this, 'configureMailer') ); } public function configureMailer(sfEvent $event) { $mailer = $event->getSubject(); // do something with the mailer } } La section suivante illustre un usage avancé et pratique de cette technique. Utiliser des Plugins Swift Mailer~ L'utilisation des plugins de Swift Mailer s'effectue en écoutant l'évènement mailer.configure (voir la section ci-dessus). public function configureMailer(sfEvent $event) { $mailer = $event->getSubject(); $plugin = new Swift_Plugins_ThrottlerPlugin( 100, Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_MINUTE ); $mailer->registerPlugin($plugin); } La section "Plugins" de la documentation officielle de Swift Mailer décrit ce qu'il faut avoir à propos des plugins natifs. Personnaliser le Comportement de Spool L'implémentation native des spools est particulièrement simple. Chaque spool récupère tous les emails depuis une file d'attente en ordre aléatoire, avant de les envoyer un par un. Dans cette section, il s'agit d'apprendre comment implémenter un système de priorité pour la file d'attente afin de donner toutes les informations nécessaires à l'implémentation d'une logique personnalisée. Tout d'abord, il convient d'ajouter une nouvelle colonne priority au modèle de données existant. # for Propel mail_message: message: { type: blob, required: true } created_at: ~ priority: { type: integer, default: 3 } # for Doctrine MailMessage: actAs: { Timestampable: ~ } columns: message: { type: blob, notnull: true } priority: { type: integer } Lorsqu'un email est envoyé, l'en-tête de priorité de celui-ci doit être fixé. La valeur 1 représente la priorité la plus élevée. $message = $this->getMailer() ->compose('[email protected]', '[email protected]', 'Subject', 'Body') ->setPriority(1) ; $this->getMailer()->send($message); Ensuite, la méthode setMessage() par défaut doit être surchargée afin de modifier la priorité de l'objet MailMessage lui-même. // for Propel class MailMessage extends BaseMailMessage http://www.symfony-project.org/more-with-symfony/1_4/fr/04-Emails[31/12/2010 02:33:48] The More with symfony book | Les Emails | symfony | Web PHP Framework { public function setMessage($message) { $msg = unserialize($message); $this->setPriority($msg->getPriority()); return parent::setMessage($message); } } // for Doctrine class MailMessage extends BaseMailMessage { public function setMessage($message) { $msg = unserialize($message); $this->priority = $msg->getPriority(); return $this->_set('message', $message); } } Dans cet exemple, le message est linéarisé par la file d'attente. Par conséquent, il doit d'abord être délinéarisé afin d'être capable de récupérer la valeur de la priorité. Il ne reste maintenant plus qu'à ajouter une méthode qui ordonne les messages par priorité. // for Propel class MailMessagePeer extends BaseMailMessagePeer { static public function getSpooledMessages() { $c = new Criteria(); $c->addAscendingOrderByColumn(self::PRIORITY); return self::doSelect($c); } // ... } // for Doctrine class MailMessageTable extends Doctrine_Table { public function getSpooledMessages() { return $this->createQuery('m') ->orderBy('m.priority') ; } // ... } La dernière étape consiste à définir la méthode de récupération des messages dans le fichier de configuration factories.yml afin de changer la manière dont les messages sont obtenus par défaut depuis la file d'attente. spool_arguments: [ MailMessage, message, getSpooledMessages ] C'est tout ce qu'il y'a à faire. Maintenant, chaque fois que la tâche project:send-emails sera exécutée, chaque email sera expédié en fonction de sa priorité. Personnaliser le Spool avec un Critère L'exemple précédent utilise un en-tête standard de message : la priorité. En revanche, si l'on souhaite utiliser n'importe quel critère ou bien ne pas altérer le message envoyé, il convient de stocker ce critère comme un en-tête personnalisé. Il ne restera plus qu'à le retirer juste avant d'envoyer l'email. Il suffit tout d'abord d'ajouter un en-tête personnalisé au message à envoyer. public function executeIndex() http://www.symfony-project.org/more-with-symfony/1_4/fr/04-Emails[31/12/2010 02:33:48] The More with symfony book | Les Emails | symfony | Web PHP Framework { $message = $this->getMailer() ->compose('[email protected]', '[email protected]', 'Subject', 'Body') ; $message->getHeaders()->addTextHeader('X-Queue-Criteria', 'foo'); $this->getMailer()->send($message); } Enfin, il ne reste plus qu'à récupérer la valeur de cet en-tête au moment de stocker le message dans la file d'attente et supprimer le message immédiatement. public function setMessage($message) { $msg = unserialize($message); $headers = $msg->getHeaders(); $criteria = $headers->get('X-Queue-Criteria')->getFieldBody(); $this->setCriteria($criteria); $headers->remove('X-Queue-Criteria'); return parent::_set('message', serialize($msg)); } « Accroître la Productivité Widgets et Validateurs Personnalisés » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/04-Emails[31/12/2010 02:33:48] The More with symfony book | Widgets et Validateurs Personnalisés | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Widgets et Validateurs Personnalisés You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Thomas Rabaix Ce chapitre explique comment construire des widgets et validateurs personnalisés à utiliser dans le framework de formulaires. Il présentera les entrailles des classes sfWidgetForm et sfValidator, ainsi que la manière de créer des widgets simples et complexes. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Au Coeur des Widgets et Validateurs Présentation de la Classe sfWidgetForm Un objet de la classe sfWidgetForm représente une implémentation visuelle de la manière dont les données relatives seront éditées. Une chaîne de caractères par exemple pourrait être éditée à partir d'un champ texte ou bien à l'aide d'un éditeur WYSIWIG avancé. Afin d'être entièrement configurable, la classe sfWidgetForm dispose de deux propriétés importantes : les options et les attributs (attributes). Support symfony! Buy this book or donate. Les options sont utilisées pour configurer le widget. Une option peut servir par exemple à recevoir une requête de base de données à utiliser afin d'alimenter une liste déroulante. Les attributs (attributes) sont les attributs HTML ajoutés à l'élément lors du rendu. De plus, la classe sfWidgetForm implémente deux méthodes importantes : La méthode configure() définit les options optionnelles et celles qui sont obligatoires. Alors que la rédéfinition du constructeur ne constitue pas une bonne pratique en soit, la méthode configure() peut quant à elle être redéfinie en toute sécurité. La méthode render() génère la sortie HTML du widget. Elle requiert un premier paramètre obligatoire, le nom du widget, et un second paramètre optionnel, sa valeur. Un objet sfWidgetForm ne sait absolument rien de son nom ou de sa valeur. Le composant est seulement responsable du rendu du widget. Le nom et la valeur sont gérés par un objet sfFormFieldSchema qui fait le lien entre les données et les widgets. Présentation de la Classe sfValidatorBase La classe sfValidatorBase est la classe de base pour tous les validateurs. Sa méthode sfValidatorBase::clean() est la plus importante dans la mesure où elle vérifie si la valeur est valide en fonction des options fournies. A l'intérieur, la méthode clean() exécute plusieurs actions différentes : supprimer les espaces blancs en début et fin de chaînes de caractères saisies, à condition que l'option trim soit spécifiée, vérifier si la valeur est vide, appeler la méthode doClean() du validateur. La méthode doClean() est celle qui implémente la logique de validation principale. Ce n'est pas une bonne pratique de redéfinir la méthode clean(). En revanche, c'est toujours dans la méthode doClean() que doit être réalisée la logique de validation personnalisée. Un validateur peut aussi être utilisé comme un composant indépendant pour vérifier l'intégrité d'une entrée. Par exemple, le validateur sfValidatorEmail contrôlera si l'email est valide ou non. $v = new sfValidatorEmail(); try { $v->clean($request->getParameter("email")); http://www.symfony-project.org/more-with-symfony/1_4/fr/05-Custom-Widgets-and-Validators[31/12/2010 02:33:52] Chapter Content Au Coeur des Widgets et Validateurs Présentation de la Classe sfWidgetForm Présentation de la Classe sfValidatorBase L'Attribut options Construire un Widget et un Validateur Simples Le Widget Google Address Map Cas d'utilisation 1 : Cas d'utilisation 2 : Le Widget sfWidgetFormGMapAddress Le Validateur sfValidatorGMapAddress Test du Validateur Conclusion The More with symfony book | Widgets et Validateurs Personnalisés | symfony | Web PHP Framework } catch(sfValidatorError $e) { $this->forward404(); } Be trained by symfony experts Jan 24: Paris Lorsqu'un formulaire est associé aux valeurs transmises dans la requête, l'objet sfForm conserve les références aux valeurs teintées originales ainsi qu'aux valeurs filtrées. Les valeurs originales sont utilisées lorsque le formulaire est réaffiché, alors que les valeurs nettoyées sont employées par l'application (par exemple pour hydrater et sauvegarder l'objet). Feb 21: Paris Les deux objets sfWidgetForm et sfValidatorBase ont une variété d'options dont certaines sont optionnelles et d'autres obligatoires. Ces options sont définies à l'intérieur de chaque méthode configure() de chaque classe grâce aux méthodes suivantes. addOption($name, $value) définit une option avec un nom et une valeur par défaut, addRequiredOption($name) définit une option obligatoire. Ces deux méthodes sont très utiles dans la mesure où elles s'assurent que les valeurs respectives sont correctement transmises au validateur ou au widget. Construire un Widget et un Validateur Simples Cette section décrit comment construire un widget simple. Ce widget particulier sera intitulé widget "Trilean", et affichera une liste déroulante, de type select, composée de trois choix possibles : No, Yes et Null. class sfWidgetFormTrilean extends sfWidgetForm { public function configure($options = array(), $attributes = array()) { $this->addOption('choices', array( 0 => 'No', 1 => 'Yes', 'null' => 'Null' )); } public function render($name, $value = null, $attributes = array(), $errors = array()) { $value = $value === null ? 'null' : $value; $options = array(); foreach ($this->getOption('choices') as $key => $option) { $attributes = array('value' => self::escapeOnce($key)); if ($key == $value) { $attributes['selected'] = 'selected'; } $options[] = $this->renderContentTag( 'option', self::escapeOnce($option), $attributes ); } return $this->renderContentTag( 'select', "\n".implode("\n", $options)."\n", array_merge(array('name' => $name), $attributes )); } } http://www.symfony-project.org/more-with-symfony/1_4/fr/05-Custom-Widgets-and-Validators[31/12/2010 02:33:52] (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris L'Attribut options (Maîtrise de & Doctrine - Français) (Maîtrise de & Doctrine - Français) and more... Search powered by google The More with symfony book | Widgets et Validateurs Personnalisés | symfony | Web PHP Framework La méthode configure() définit la liste des valeurs des options grâce à l'option choices. Ce tableau peut être redéfini afin de modifier le label associé de chaque valeur par exemple. Il n'y a pas de limite au nombre d'options qu'un widget peut déclarer. Néanmoins, la classe de base des widgets déclare quelques options standards qui sont donc des options réservées de-facto. id_format : le format des identifiants, %s par défaut ; is_hidden : valeur booléenne qui définit si le widget est un champ caché, utilisée notamment par la méthode sfForm::renderHiddenFields() pour générer tous les champs cachés en une seule fois ; needs_multipart: valeur booléenne qui détermine si la balise du formulaire doit inclure l'option multipart. Cette option est nécessaire lorsqu'il s'agit de réaliser des envois de fichiers ; default: la valeur par défaut qui doit être utilisée pour rendre le widget si aucune valeur n'a été fournie ; label: le label par défaut du widget. La méthode render() génère le HTML correspondant pour une liste déroulante select. Elle appelle la méthode interne renderContentTag() qui facilite la génération de balises HTML. Le widget est désormais complet et son validateur associé peut quant à lui être défini dès à présent. class sfValidatorTrilean extends sfValidatorBase { protected function configure($options = array(), $messages = array()) { $this->addOption('true_values', array('true', 't', 'yes', 'y', 'on', '1')); $this->addOption('false_values', array('false', 'f', 'no', 'n', 'off', '0')); $this->addOption('null_values', array('null', null)); } protected function doClean($value) { if (in_array($value, $this->getOption('true_values'))) { return true; } if (in_array($value, $this->getOption('false_values'))) { return false; } if (in_array($value, $this->getOption('null_values'))) { return null; } throw new sfValidatorError($this, 'invalid', array('value' => $value)); } public function isEmpty($value) { return false; } } Le validateur sfValidatorTrilean définit trois options dans sa méthode configure(). Chaque option est un jeu de valeurs possibles. Comme elles sont définies comme des options, le développeur a la possibilité de personnaliser les valeurs en fonction des spécifications techniques. La méthode doClean() vérifie si la valeur fournie correspond à une valeur valide, puis retourne la bonne valeur filtrée parmi les trois. Si aucune valeur ne correspond alors la méthode lance une exception sfValidatorError qui correspond à la classe standard pour toute les exceptions de validation. La dernière méthode, isEmpty(), est écrasée car le comportement par défaut est de retourner true si null est fourni en entrée. Comme le widget en cours autorise la valeur null, cette méthode doit toujours retourner false dans ce cas. http://www.symfony-project.org/more-with-symfony/1_4/fr/05-Custom-Widgets-and-Validators[31/12/2010 02:33:52] The More with symfony book | Widgets et Validateurs Personnalisés | symfony | Web PHP Framework Si isEmpty() retourne true, la méthode doClean() du validateur n'est jamais appelée. Ce premier widget était très simple à réaliser, cependant, il a introduit certaines bases importantes pour la suite. Le Widget Google Address Map Dans cette partie, il s'agit d'expliquer comment créer un widget beaucoup plus complexe avec plusieurs champs et avec des interactions JavaScript. Le widget s'appellera "GMAW" pour "Google Map Address Widget". Quel est le but de ce composant ? Le widget doit fournir une méthode simple pour que l'utilisateur final puisse ajouter une adresse en utilisant un champ texte et une carte du service "Google Map". Cas d'utilisation 1 : L'utilisateur saisit une adresse. L'utilisateur clique sur le bouton "rechercher". Les champs cachés longitude et latitude sont mis à jour et un marqueur est ajouté sur la carte. Le marqueur est positionné sur la localisation de l'adresse. Si le service de géopositionnement ne peut trouver cette adresse alors un message d'erreur doit apparaître. Cas d'utilisation 2 : L'utilisateur clique sur la carte La latitude et la longitude sont mises à jour. Une demande d'adresse est envoyée au service de géo-positionnement. Les champs suivants ont besoin d'être envoyés et gérés par le formulaire : latitude : nombre à virgule flottante, entre 90 et -90 ; longitude : nombre à virgule flottante, entre 180 et -180 ; address : chaîne de caractères, texte seulement. Les spécifications fonctionnelles sont maintenant définies, et voici la liste des éléments techniques utilisés dans la suite de ce chapitre : Services Google Maps et Geocoding : affichent la carte et récupèrent les informations d'une adresse, jQuery : gère les interactions javascript entre le formulaire et la carte, sfForm : rend la carte et les champs textes. Le Widget sfWidgetFormGMapAddress Comme indiqué précédemment, un widget est seulement la représentation visuelle des données à éditer, la méthode configure() de ce widget doit avoir toutes les options nécessaires afin de configurer la carte Google ou bien pour modifier les styles de chaque élément. L'option la plus importante est ici l'option template.html qui définit comment les éléments sont ordonnés. Lors de la création d'un widget il est très important de prévoir de la souplesse afin de réutiliser le composant sur d'autres pages. Un autre point important à noter est la définition des médias externes utilisés par le widget. Là encore, le framework de formulaires permet d'implémenter deux méthodes : getJavascripts() : retourne un tableau de fichiers JavaScript, http://www.symfony-project.org/more-with-symfony/1_4/fr/05-Custom-Widgets-and-Validators[31/12/2010 02:33:52] The More with symfony book | Widgets et Validateurs Personnalisés | symfony | Web PHP Framework getStylesheets() : retourne un tableau de feuilles de style où la clé du tableau est le chemin du fichier et la valeur le type de media. Pour fonctionner correctement, le widget a besoin de code JavaScript dans le but de gérer les interactions entre la carte Google et les champs du widget. Le widget doit donc seulement implémenter la méthode getJavascript(). Le widget n'est pas responsable du chargement des services Google. Il est en effet de la responsabilité du développeur d'insérer les bonnes informations relatives à l'API Google. Le widget n'est pas forcément le seul élément sur une page à utiliser ces services, il faut donc découpler cette fonction du widget. class sfWidgetFormGMapAddress extends sfWidgetForm { public function configure($options = array(), $attributes = array()) { $this->addOption('address.options', array('style' => 'width:400px')); $this->setOption('default', array( 'address' => '', 'longitude' => '2.294359', 'latitude' => '48.858205' )); $this->addOption('div.class', 'sf-gmap-widget'); $this->addOption('map.height', '300px'); $this->addOption('map.width', '500px'); $this->addOption('map.style', ""); $this->addOption('lookup.name', "Lookup"); $this->addOption('template.html', ' <div id="{div.id}" class="{div.class}"> {input.search} <input type="submit" value="{input.lookup.name}" id="{input.lookup.id}" /> <br /> {input.longitude} {input.latitude} <div id="{map.id}" style="width:{map.width};height:{map.height};{map.style}"></div> </div> '); $this->addOption('template.javascript', ' <script type="text/javascript"> jQuery(window).bind("load", function() { new sfGmapWidgetWidget({ longitude: "{input.longitude.id}", latitude: "{input.latitude.id}", address: "{input.address.id}", lookup: "{input.lookup.id}", map: "{map.id}" }); }) </script> '); } public function getJavascripts() { return array( '/sfFormExtraPlugin/js/sf_widget_gmap_address.js' ); } public function render($name, $value = null, $attributes = array(), $errors = array()) { // define main template variables $template_vars = array( '{div.id}' => $this->generateId($name), '{div.class}' => $this->getOption('div.class'), '{map.id}' => $this->generateId($name.'[map]'), '{map.style}' => $this->getOption('map.style'), http://www.symfony-project.org/more-with-symfony/1_4/fr/05-Custom-Widgets-and-Validators[31/12/2010 02:33:52] The More with symfony book | Widgets et Validateurs Personnalisés | symfony | Web PHP Framework '{map.height}' '{map.width}' '{input.lookup.id}' '{input.lookup.name}' '{input.address.id}' '{input.latitude.id}' '{input.longitude.id}' => => => => => => => $this->getOption('map.height'), $this->getOption('map.width'), $this->generateId($name.'[lookup]'), $this->getOption('lookup.name'), $this->generateId($name.'[address]'), $this->generateId($name.'[latitude]'), $this->generateId($name.'[longitude]'), ); // vérifie si la valeur est valide $value = !is_array($value) ? array() : $value; $value['address'] = isset($value['address']) ? $value['address'] : ''; $value['longitude'] = isset($value['longitude']) ? $value['longitude'] : ''; $value['latitude'] = isset($value['latitude']) ? $value['latitude'] : ''; // définit le widget pour le champ adresse $address = new sfWidgetFormInputText(array(), $this>getOption('address.options')); $template_vars['{input.search}'] = $address->render($name.'[address]', $value['address']); // définit les widgets pour les champs : longitude et latitude $hidden = new sfWidgetFormInputHidden; $template_vars['{input.longitude}'] = $hidden->render($name.'[longitude]', $value['longitude']); $template_vars['{input.latitude}'] = $hidden->render($name.'[latitude]', $value['latitude']); // assemble le modèle avec les valeurs return strtr( $this->getOption('template.html').$this->getOption('template.javascript'), $template_vars ); } } Le widget fait appel à la méthode generateId() pour générer le champ id de chaque élément. La variable $name est fournie par la classe sfFormFieldSchema. Par conséquent, le nom du widget est composé du nom du formulaire, des schémas de widgets imbriqués et finalement du nom du champ défini par la méthode configure(). Par exemple, si le nom du formulaire est user, le widget schéma imbriqué (via un formulaire imbriqué) est location, et le nom du champ est address. Alors le nom final du champ sera user[location][address] et l'id sera user_location_address. Pour résumer, $this>generateId($name.'[latitude]') générera un id valide et unique pour le champ latitude. Les différentes valeurs des identifiants sont importantes car elles sont passées au bloc JavaScript par l'intermédiaire de la variable template.js afin que le Javascript puisse gérer les différents éléments du widget. La méthode render() initialise deux widgets : sfWidgetFormInputText pour le champ texte de l'adresse et sfWidgetFormInputHidden pour les champs cachés longitude et latitude. Le widget peut ainsi être rapidement testé à l'aide du code ci-dessous. $widget = new sfWidgetFormGMapAddress(); echo $widget->render('user[location][address]', array( 'address' => '151 Rue montmartre, 75002 Paris', 'longitude' => '2.294359', 'latitude' => '48.858205' )); Le résultat obtenu est le suivant : <div id="user_location_address" class="sf-gmap-widget"> <input style="width:400px" type="text" name="user[location][address][address]" value="151 Rue montmartre, 75002 Paris" id="user_location_address_address" /> <input type="submit" value="Lookup" id="user_location_address_lookup" /> <br /> <input type="hidden" name="user[location][address][longitude]" value="2.294359" id="user_location_address_longitude" /> http://www.symfony-project.org/more-with-symfony/1_4/fr/05-Custom-Widgets-and-Validators[31/12/2010 02:33:52] The More with symfony book | Widgets et Validateurs Personnalisés | symfony | Web PHP Framework <input type="hidden" name="user[location][address][latitude]" value="48.858205" id="user_location_address_latitude" /> <div id="user_location_address_map" style="width:500px;height:300px;"></div> </div> <script type="text/javascript"> jQuery(window).bind("load", function() { new sfGmapWidgetWidget({ longitude: "user_location_address_longitude", latitude: "user_location_address_latitude", address: "user_location_address_address", lookup: "user_location_address_lookup", map: "user_location_address_map" }); }) </script> La partie JavaScript du widget utilise les différents attributs id et les attache avec jQuery à des écouteurs, listeners, qui seront activés à l'occasion de différentes interactions utilisateur. Le JavaScript met à jour les différents champs tels que les valeurs de la longitude et de la latitude ainsi que l'adresse si l'utilisateur clique dessus. L'objet JavaScript dispose de trois méthodes intéressantes : init() initialise les variables et les événements ; lookupCallback(), méthode statique utilisée pour trouver la longitude et la latitude en fonction de l'adresse ; reverseLookupCallback(), méthode statique utilisée pour retrouver l'adresse à partir de la longitude et de la latitude. Le code JavaScript se trouve dans l'Annexe A. Toutes les informations complémentaires sur le fonctionnement des services Google peuvent être trouvées sur le site officiel de Google Maps API. Le Validateur sfValidatorGMapAddress Le validateur doit vérifier plusieurs points importants. Pour rappel, il est impossible de faire confiance aux valeurs fournies par un utilisateur. C'est pourquoi le validateur doit s'assurer que la valeur du champ est correcte. Cette dernière doit être un tableau associatif contenant une longitude, une latitude et une adresse. C'est pour cette raison que le validateur principal instancie d'autres validateurs auxquels il délègue la validation de chaque élément du tableau. class sfValidatorGMapAddress extends sfValidatorBase { protected function doClean($value) { if (!is_array($value)) { throw new sfValidatorError($this, 'invalid'); } try { $latitude = new sfValidatorNumber(array( 'min' => -90, 'max' => 90, 'required' => true )); $value['latitude'] = $latitude->clean(isset($value['latitude']) ? $value['latitude'] : null); $longitude = new sfValidatorNumber(array( 'min' => -180, 'max' => 180, 'required' => true )); $value['longitude'] = $longitude->clean(isset($value['longitude']) ? $value['longitude'] : null); $address = new sfValidatorString(array( 'min_length' => 10, 'max_length' => 255, 'required' => true )); $value['address'] = $address->clean(isset($value['address']) ? $value['address'] : null); } catch(sfValidatorError $e) { throw new sfValidatorError($this, 'invalid'); } http://www.symfony-project.org/more-with-symfony/1_4/fr/05-Custom-Widgets-and-Validators[31/12/2010 02:33:52] The More with symfony book | Widgets et Validateurs Personnalisés | symfony | Web PHP Framework return $value; } } En cas d'erreur, un validateur lance toujours une exception de type sfValidatorError. C'est pour cette raison que le code de validation de chaque valeur du tableau est encapsulé dans un bloc try/catch. Une exception globale de type invalid est ensuite retournée en cas d'erreur sur l'un des champs. Test du Validateur Pourquoi tester ? Le validateur est le lien entre les valeurs saisies par l'utilisateur et l'application. Si le validateur n'est pas fiable, alors l'application est vulnérable. Heureusement, symfony est livré avec une librairie de test très facile à utiliser. Comment peut-on tester un validateur ? Comme expliqué plus haut, un validateur jette une exception en cas d'erreur. Le test doit donc injecter des valeurs correctes et invalides au validateur et vérifier la présence de l'exception. $t = new lime_test(7, new lime_output_color()); $tests = array( array(false, '', 'empty value'), array(false, 'string value', 'string value'), array(false, array(), 'empty array'), array(false, array('address' => 'my awesome address'), 'incomplete address'), array(false, array('address' => 'my awesome address', 'latitude' => 'String', 'longitude' => 23), 'invalid values'), array(false, array('address' => 'my awesome address', 'latitude' => 200, 'longitude' => 23), 'invalid values'), array(true, array('address' => 'my awesome address', 'latitude' => '2.294359', 'longitude' => '48.858205'), 'valid value') ); $v = new sfValidatorGMapAddress(); $t->diag("Testing sfValidatorGMapAddress"); foreach($tests as $test) { list($validity, $value, $message) = $test; try { $v->clean($value); $catched = false; } catch(sfValidatorError $e) { $catched = true; } $t->ok($validity != $catched, '::clean() '.$message); } Quand la méthode sfForm::bind() est appelée, la méthode clean() de chaque validateur est alors invoquée. Par conséquent, il est facile de reproduire ce comportement en instanciant directement le validateur sfValidatorGMapAddress. Conclusion L'erreur la plus courante lors de la création d'un widget réside dans le fait d'être trop concentré sur la manière dont sont stockées les valeurs en base de données. Cependant le framework de formulaires se comporte à la fois comme un conteneur et un validateur de données. Par conséquent, un widget doit seulement gérer ces informations. Si les données sont valides alors les différentes valeurs peuvent être utilisées dans un modèle de données ou dans le contrôleur. http://www.symfony-project.org/more-with-symfony/1_4/fr/05-Custom-Widgets-and-Validators[31/12/2010 02:33:52] The More with symfony book | Widgets et Validateurs Personnalisés | symfony | Web PHP Framework « Les Emails Formulaires Avancés » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/05-Custom-Widgets-and-Validators[31/12/2010 02:33:52] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Formulaires Avancés You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Ryan Weaver, Fabien Potencier Le framework de formulaires de symfony équipe le développeur des outils nécessaires à l'affichage et à la validation des données dans un problème orienté objet. Grâce aux classes sfFormDoctrine et sfFormPropel proposées par chaque ORM, le framework de formulaires peut facilement afficher et sauvegarder des formulaires liés au modèle de données. Toutefois, des situations courantes demandent au développeur de personnaliser et d'étendre des formulaires. Ce chapitre présentera et résoudra quelques uns des problèmes complexes récurrents liés aux formulaires. L'objet sfForm sera quant à lui disséqué afin de lever une partie du mystère. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Mini-Projet : Produits et Photos Le premier problème concerne l'édition d'un produit individuel et d'un nombre de photos illimité pour ce produit. L'utilisateur doit pouvoir modifier le produit et ses photos associées sur le même formulaire. Il s'agit de permettre à l'utilisateur d'envoyer jusqu'à deux photos du produit à la fois. Le modèle de données ci-dessous présente une implémentation potentielle pour résoudre ce problème. Product: columns: name: price: ProductPhoto: columns: product_id: filename: caption: relations: Product: alias: foreignType: foreignAlias: onDelete: { type: string(255), notnull: true } { type: decimal, notnull: true } { type: integer } { type: string(255) } { type: string(255), notnull: true } Product many Photos cascade Lorsqu'il sera terminé, le formulaire ressemblera à la capture d'écran ci-après. Chapter Content Mini-Projet : Produits et Photos Apprendre par l'Exemple Configuration de Base des Formulaires Imbriquer les Formulaires Remaniement Dissection de l'Objet sfForm Un Formulaire est un Tableau Dissection de la Classe ProductForm La Méthode sfForm::embedForm() en Coulisses Afficher des Formulaires Imbriqués dans la Vue Afficher chaque Champ du Formulaire avec sfFormField Apprendre par l'Exemple http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] Les Méthodes de Rendu de sfFormField The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework Le meilleur moyen d'apprendre les techniques avancées d'usage des formulaires est bien entendu de suivre le déroulement de ce chapitre, et de tester les exemples présentés étape par étape. Grâce à la fonctionnalité --installer de symfony, le framework offre la possibilité de créer un projet fonctionnel accompagné d'une base de données SQLite prête à l'emploi. Ce projet intègre un modèle de base de données Doctrine, quelques données de test, une application frontend et un module product pour travailler. Le script d'installation est disponible en téléchargement et s'exécute à l'aide de la commande suivante afin de générer la base du projet symfony. $ php symfony generate:project advanced_form -installer=/path/to/advanced_form_installer.php Cette commande crée un projet complet et fonctionnel à partir du schéma de base de données étudié dans la section précédente. Rendu d'un Nouveau ProductForm Sauvegarder des Formulaires d'Objets Le Processus de Sauvegarde du Formulaire Ignorer les Formulaires Imbriqués Création d'un Validateur Personnalisé Imbriquer Facilement des Formulaires Doctrine Les Evénements de Formulaire Enregistrement d'Erreurs Personnalisées via form.validation_error Styles Personnalisés des Erreurs d'un Champ de Formulaire Conclusion Dans ce chapitre, les chemins des fichiers correspondent à un projet symfony utilisant Doctrine dans la mesure où il a été généré par la commande précédente. Configuration de Base des Formulaires Puisque les besoins entraînent des changements sur deux modèles différents, Product et ProductPhoto, la solution oblige à contenir deux formulaires symfony (ProductForm et ProductPhotoForm). Heureusement, le framework de formulaires peut facilement combiner plusieurs formulaires en un seul via la méthode sfForm::embedForm(). Il s'agit tout d'abord de configurer la classe ProductPhotoForm. Dans cet exemple, c'est le champ filename qui est utilisé comme champ d'envoi de fichiers. // lib/form/doctrine/ProductPhotoForm.class.php public function configure() { $this->useFields(array('filename', 'caption')); $this->setWidget('filename', new sfWidgetFormInputFile()); $this->setValidator('filename', new sfValidatorFile(array( 'mime_types' => 'web_images', 'path' => sfConfig::get('sf_upload_dir').'/products', ))); } Pour ce formulaire, les deux champs caption et filename sont requis par défaut, mais pour des raisons différentes. Le champ caption est obligatoire car la colonne relative en base de données a été définie avec la propriété notnull à true. Le champ filename est quant à lui obligatoire par défaut car un objet validateur a toujours l'option required à true par défaut. sfForm::useFields() est une nouvelle méthode de symfony 1.3 qui permet au développeur de spécifier exactement les champs que le formulaire devra utiliser et l'ordre dans lequel ils seront affichés. Tous les autres champs non affichés seront retirés du formulaire. Jusqu'à présent, rien de particulier n'a été réalisé si ce n'est une configuration ordinaire du formulaire. Il s'agit maintenant de combiner les formulaires en un seul. Imbriquer les Formulaires En invoquant la méthode sfForm::embedForm(), les formulaires indépendants ProductForm et ProductPhotoForms peuvent être combinés très facilement. Le travail est effectué dans le formulaire principal, ProductForm dans cet exemple. Les besoins fonctionnels spécifient que l'utilisateur final doit être capable d'envoyer jusqu'à deux photos d'un même produit à la fois. Pour ce faire, deux objets ProductPhotoForm seront embarqués dans l'objet ProductForm. // lib/form/doctrine/ProductForm.class.php public function configure() { $subForm = new sfForm(); for ($i = 0; $i < 2; $i++) { $productPhoto = new ProductPhoto(); $productPhoto->Product = $this->getObject(); http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Search powered by google The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework $form = new ProductPhotoForm($productPhoto); $subForm->embedForm($i, $form); } $this->embedForm('newPhotos', $subForm); } En accédant directement au module product depuis un navigateur, l'utilisateur a désormais la possibilité de soumettre deux objets ProductPhoto mais également de modifier l'objet Product lui-même. Symfony sauvegarde automatiquement les nouveaux objets ProductPhoto et les relie à l'objet Product correspondant. L'envoi de fichiers défini dans la classe ProductPhotoForm fonctionne lui aussi normalement. A ce stade, il s'agit de vérifier que les enregistrements ont été correctement sauvegardés en base de données. $ php symfony doctrine:dql --table "FROM Product" $ php symfony doctrine:dql --table "FROM ProductPhoto" Il est intéressant de remarquer les noms des photos dans la table ProductPhoto. Tout fonctionne comme prévu à condition de trouver des fichiers avec les mêmes noms que ceux de la base de données dans le répertoire web/uploads/products/. Etant donnés que les champs filename et caption sont requis dans ProductPhotoForm, la validation du formulaire principal échouera tout le temps tant que l'utilisateur n'enverra pas deux nouvelles photos. La suite de ce chapitre explique comment résoudre ce problème. Remaniement Bien que le formulaire précédent se comporte comme prévu, il serait néanmoins plus judicieux de factoriser le code afin de faciliter l'écriture de tests. De plus, cette pratique permet de réutiliser le code plus aisément. Tout d'abord, il s'agit de créer un nouveau formulaire qui représente une collection d'objets ProductPhotoForm en s'appuyant sur le code écrit jusqu'à maintenant. // lib/form/doctrine/ProductPhotoCollectionForm.class.php class ProductPhotoCollectionForm extends sfForm { public function configure() { if (!$product = $this->getOption('product')) { throw new InvalidArgumentException('You must provide a product object.'); } for ($i = 0; $i < $this->getOption('size', 2); $i++) { $productPhoto = new ProductPhoto(); $productPhoto->Product = $product; $form = new ProductPhotoForm($productPhoto); $this->embedForm($i, $form); } } } Ce formulaire nécessite deux options : product : le produit pour lequel la collection d'objets ProductPhotoForm doit être créée ; size: le nombre d'objets ProductPhotoForm à créer, deux par défaut. La méthode configure() de la classe ProductForm peut être alors être modifiée comme suit. // lib/form/doctrine/ProductForm.class.php public function configure() { $form = new ProductPhotoCollectionForm(null, array( 'product' => $this->getObject(), 'size' => 2, )); http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework $this->embedForm('newPhotos', $form); } Dissection de l'Objet sfForm En réalité, un formulaire web est une collection de champs qui sont affichés et envoyés au serveur. Dans le même esprit, l'objet sfForm est essentiellement un tableau de champs de formulaire. Alors que sfForm s'occupe du processus, les champs individuels sont responsables de définir comment chacun doit s'afficher et être validé. Dans symfony, chaque champ de formulaire est défini à l'aide de deux objets distincts : Un widget qui affiche le code XHTML du champ ; Un validateur qui nettoie et valide les données envoyées. Dans symfony, un widget est défini comme n'importe quel objet dont la seule finalité est d'afficher du code XHTML. Bien qu'ils soient couramment utilisés dans les formulaires, les widgets peuvent être créés pour afficher n'importe quelle balise. Un Formulaire est un Tableau Pour rappel, un objet sfForm est essentiellement un "tableau de champs de formulaires". Pour être plus précis, l'objet sfForm abrite un tableau de widgets et un tableau de validateurs pour tous les champs du formulaire. Ces deux tableaux, appelés widgetSchema etvalidatorSchema, sont des propriétés de la classe sfForm. Pour ajouter un champ au formulaire, il suffit d'ajouter simplement le widget du champ dans le tableau widgetSchema et le validateur du champ dans le tableau validatorSchema. Par exemple, le code suivant déclare un champ email dans le formulaire. public function configure() { $this->widgetSchema['email'] = new sfWidgetFormInputText(); $this->validatorSchema['email'] = new sfValidatorEmail(); } Les tableaux widgetSchema et validatorSchema sont en réalité des classes spéciales appelées sfWidgetFormSchema et sfValidatorSchema qui implémentent l'interface ArrayAccess. Dissection de la Classe ProductForm Comme la classe ProductForm étend fatalement la classe sfForm, elle abrite tous les widgets et validateurs dans les tableaux widgetSchema et validatorSchema. Le listing ci-dessous décrit l'organisation générale de chaque tableau dans un objet ProductForm entièrement élaboré. widgetSchema => array ( [id] => sfWidgetFormInputHidden, [name] => sfWidgetFormInputText, [price] => sfWidgetFormInputText, [newPhotos] => array( [0] => array( [id] => sfWidgetFormInputHidden, [filename] => sfWidgetFormInputFile, [caption] => sfWidgetFormInputText, ), [1] => array( [id] => sfWidgetFormInputHidden, [filename] => sfWidgetFormInputFile, [caption] => sfWidgetFormInputText, ), ), ) validatorSchema => array ( [id] => sfValidatorDoctrineChoice, [name] => sfValidatorString, http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework [price] => sfValidatorNumber, [newPhotos] => array( [0] => array( [id] => sfValidatorDoctrineChoice, [filename] => sfValidatorFile, [caption] => sfValidatorString, ), [1] => array( [id] => sfValidatorDoctrineChoice, [filename] => sfValidatorFile, [caption] => sfValidatorString, ), ), ) Comme les objets widgetSchema et validatorSchema se comportent tels des tableaux, les tableaux ci-dessus définis par les clés newPhotos, 0 et 1 sont aussi des objets sfWidgetSchema et sfValidatorSchema. Comme prévu, les champs basiques (id, name et price) sont représentés au premier niveau de chaque tableau. Dans un formulaire sans formulaire imbriqué, les tableaux widgetSchema et validatorSchema ont un seul niveau qui représente les champs de base du formulaire. Les widgets et validateurs de n'importe quel formulaire embarqué sont représentés comme des sous-tableaux dans widgetSchema et validatorSchema comme cela a été démontré précédemment. La méthode qui s'occupe de ce processus est expliquée après. La Méthode sfForm::embedForm() en Coulisses Il convient de garder à l'esprit qu'un formulaire est composé d'un tableau de widgets et d'un tableau de validateurs. Embarquer un formulaire dans un autre signifie essentiellement que les tableaux des widgets et des validateurs d'un formulaire seront ajoutés dans les tableaux des widgets et des validateurs du formulaire principal. Cette tâche est entièrement effectuée par la méthode sfForm::embedForm(). Le résultat est toujours une addition multidimensionnelle des tableaux widgetSchema et validatorSchema. Maintenant, c'est au tour de la configuration de l'objet ProductPhotoCollectionForm d'être étudiée car c'est elle qui lie les objets ProductPhotoForm. Ce formulaire du milieu agit comme un formulaire d'adaptation et aide à son organisation. Il convient alors de commencer par l'étude du code suivant de la méthode ProductPhotoCollectionForm::configure(). $form = new ProductPhotoForm($productPhoto); $this->embedForm($i, $form); Le formulaire ProductPhotoCollectionForm commence lui-même comme un nouvel objet sfForm. En tant que tels, les tableaux widgetSchema et validatorSchema sont vides. widgetSchema => array() validatorSchema => array() Cependant, l'objet ProductPhotoForm est déjà préparé avec trois champs (id, filename et caption), et trois entrées correspondantes dans ses tableaux widgetSchema et validatorSchema. widgetSchema => array ( [id] => sfWidgetFormInputHidden, [filename] => sfWidgetFormInputFile, [caption] => sfWidgetFormInputText, ) validatorSchema => array ( [id] => sfValidatorDoctrineChoice, [filename] => sfValidatorFile, [caption] => sfValidatorString, ) La méthode sfForm::embedForm() ajoute simplement les tableaux widgetSchema et validatorSchema de chaque ProductPhotoForm aux tableaux widgetSchema et validatorSchema de l'objet ProductPhotoCollectionForm vide. http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework Une fois terminés, les tableaux widgetSchema et validatorSchema du formulaire d'adaptation (ProductPhotoCollectionForm) deviennent des tableaux multi-dimensionnels contenant les widgets et les validateurs des deux objets ProductPhotoForm. widgetSchema => array ( [0] => array ( [id] => sfWidgetFormInputHidden, [filename] => sfWidgetFormInputFile, [caption] => sfWidgetFormInputText, ), [1] => array ( [id] => sfWidgetFormInputHidden, [filename] => sfWidgetFormInputFile, [caption] => sfWidgetFormInputText, ), ) validatorSchema => array ( [0] => array ( [id] => sfValidatorDoctrineChoice, [filename] => sfValidatorFile, [caption] => sfValidatorString, ), [1] => array ( [id] => sfValidatorDoctrineChoice, [filename] => sfValidatorFile, [caption] => sfValidatorString, ), ) Dans la dernière étape du processus, le formulaire d'adaptation résultant, ProductPhotoCollectionForm est embarqué directement dans l'objet ProductForm. Cela se produit dans la méthode ProductForm::configure() qui tire profit de tout le travail réalisé dans l'objet ProductPhotoCollectionForm. $form = new ProductPhotoCollectionForm(null, array( 'product' => $this->getObject(), 'size' => 2, )); $this->embedForm('newPhotos', $form); Ceci établit la dernière structure des tableaux widgetSchema et validatorSchema vus ci-dessus. A noter que la méthode embedForm() est très proche du simple fait de la combinaison manuelle des tableaux widgetSchema et validatorSchema. $this->widgetSchema['newPhotos'] = $form->getWidgetSchema(); $this->validatorSchema['newPhotos'] = $form->getValidatorSchema(); Afficher des Formulaires Imbriqués dans la Vue Le template actuel _form.php du module product ressemble sensiblement au code ci-dessous : // apps/frontend/module/product/templates/_form.php <!-- ... --> <tbody> <?php echo $form ?> </tbody> <!-- ... --> La ligne <?php echo $form ?> est à la fois la façon la plus simple d'afficher un formulaire, et la plus compliquée. Elle est d'une grande utilité lorsqu'il s'agit de réaliser un prototype. Or, dès qu'un changement de l'agencement est nécessaire, elle doit être remplacée par un code spécifique à l'affichage désiré. Elle peut alors être supprimée dans la mesure où elle sera de http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework toute manière modifiée dans cette section. La chose la plus importante à comprendre lorsqu'il s'agit d'afficher un formulaire imbriqué dans la vue est l'organisation du tableau multidimensionnel widgetSchema expliquée dans la section précédente. Pour cet exemple, l'objectif consiste à commencer par afficher les champs de base name et price de l'objet ProductForm dans la vue. // apps/frontend/module/product/templates/_form.php <?php echo $form['name']->renderRow() ?> <?php echo $form['price']->renderRow() ?> <?php echo $form->renderHiddenFields() ?> Comme son nom l'indique, la méthode renderHiddenFields() génère tous les champs cachés du formulaire. Le code des actions n'a pas été exposé volontairement car il ne nécessite pas d'attention particulière. Il suffit de regarder le fichier d'actions apps/frontend/modules/product/actions/actions.class.php pour s'en persuader. Il ressemble en effet à n'importe quel CRUD et peut être généré automatiquement à l'aide de la tâche doctrine:generate-module. La classe sfForm abrite désormais les tableaux widgetSchema et validatorSchema qui définissent les champs. De plus, la classe sfForm implémente la classe native ArrayAccess de PHP 5, ce qui signifie que les champs du formulaire sont directement accessibles par l'intermédiaire de la syntaxe des clés de tableaux vue précédemment. L'affichage des champs un par un nécessite d'accéder à un champ de manière unique en appelant sa méthode renderRow(). Mais quel est le type de l'objet $form['name'] ? Alors que la réponse se pourrait d'être le widget sfWidgetFormInputText pour le champ name, elle est en réalité sensiblement différente. Afficher chaque Champ du Formulaire avec sfFormField En utilisant les tableaux widgetSchema et validatorSchema définis dans chaque classe de formulaire, sfForm génère automatiquement un troisième tableau appelé sfFormFieldSchema. Ce tableau contient un objet spécial pour chaque champ qui agit comme une classe helper responsable de l'affichage du champ. L'objet, de type sfFormField, est une combinaison d'un widget et d'un validateur pour chaque champ, et est créé automatiquement. <?php echo $form['name']->renderRow() ?> Dans le morceau de code précédent, $form['name'] est un objet de type sfFormField qui abrite la méthode renderRow() avec plusieurs autres fonctions de rendu utiles. Les Méthodes de Rendu de sfFormField Chaque objet de type sfFormField peut être utilisé pour générer le rendu de tous les aspects du champ qu'il représente. Par exemple, le champ lui même, le label, les messages d'erreurs etc. Voici quelques méthodes utiles de l'objet sfFormField. Les autres peuvent être trouvées via l'API en ligne de symfony 1.3. sfFormField->render() génère le champ du formulaire (par exemple input, select) avec les bonnes valeurs en utilisant l'objet widget du champ ; sfFormField->renderError() génère toutes les erreurs de validation sur le champ en utilisant l'objet validateur du champ ; sfFormField->renderRow() est une méthode englobante qui affiche le label, le champ du formulaire, l'erreur et le message d'aide. En réalité, chaque méthode d'affichage de la classe sfFormField utilise également des informations de la propriété widgetSchema du formulaire. C'est le cas par exemple de l'objet sfWidgetFormSchema qui abrite tous les widgets du formulaire. Cette classe aide à la génération des attributs name et id de chaque champ, garde une trace du label pour chaque champ et définit la balise XHTML utilisée avec renderRow(). Il est important de noter que le tableau formFieldSchema reflète toujours la structure des tableaux widgetSchema et validatorSchema du formulaire. Par exemple, le tableau http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework formFieldSchema d'un objet ProductForm complet aura la structure suivante, qui est la clé du rendu de chaque champ dans la vue. formFieldSchema => array ( [id] => sfFormField [name] => sfFormField, [price] => sfFormField, [newPhotos] => array( [0] => array( [id] => sfFormField, [filename] => sfFormField, [caption] => sfFormField, ), [1] => array( [id] => sfFormField, [filename] => sfFormField, [caption] => sfFormField, ), ), ) Rendu d'un Nouveau ProductForm En utilisant le tableau ci-dessus comme carte, il est facile d'afficher les champs du formulaire embarqué ProductPhotoForm dans la vue en localisant et en affichant l'objet sfFormField. // apps/frontend/module/product/templates/_form.php <?php foreach ($form['newPhotos'] as $photo): ?> <?php echo $photo['caption']->renderRow() ?> <?php echo $photo['filename']->renderRow() ?> <?php endforeach; ?> Le bloc de code ci-dessus itère à deux reprises : une fois pour le tableau à l'index 0 et une seconde fois pour le tableau à l'index 1. Comme l'illustrait le diagramme ci-dessus, les objets sous-jacents de chaque tableau sont de type sfFormField, qui peuvent donc être affichés comme n'importe quel autre champ. Sauvegarder des Formulaires d'Objets Dans la plupart des cas, un formulaire repose directement sur une ou plusieurs tables de la base de données, et entraîne des changements sur les données dans ces tables en fonction des valeurs envoyées. Symfony génère automatiquement un objet de formulaire pour chaque modèle du schéma, qui étend soit sfFormDoctrine ou sfFormPropel en fonction de l'ORM choisi. Chaque classe de formulaire est similaire et permet finalement aux valeurs transmises de rester en base de données. sfFormObject est une nouvelle classe ajoutée dans symfony 1.3 pour gérer toutes les tâches communes de sfFormDoctrine et sfFormPropel. Chaque classe étend sfFormObject, qui s'occupe maintenant du processus de sauvegarde du formulaire décrit ci-dessous. Le Processus de Sauvegarde du Formulaire Dans cet exemple, symfony sauvegarde automatiquement les informations de l'objet Product et des nouveaux objets ProductPhoto sans autre intervention du développeur. C'est la méthode sfFormObject::save() qui exécute une multitude de méthodes en arrière plan. La compréhension de ce processus est la clé pour étendre ce traitement à des cas plus complexes. Le processus de sauvegarde du formulaire est une suite de méthodes exécutées en interne, qui se déclenchent après l'appel de la méthode sfFormObject::save(). La majorité du travail est située dans la méthode sfFormObject::updateObject() qui est appelée récursivement sur tous les formulaires imbriqués. http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework La majorité du processus de sauvegarde intervient dans la méthode sfFormObject::doSave(), qui est appelée par sfFormObject::save() et entourée par une transaction. Si le processus de sauvegarde lui-même doit être surchargé, c'est alors dans la méthode sfFormObject::doSave() que ce travail doit être réalisé. Ignorer les Formulaires Imbriqués L'implémentation actuelle de ProductForm a un inconvénient majeur. Etant donnés que les champs filename et caption sont nécessaires dans ProductPhotoForm, la validation du formulaire principal échouera à chaque fois tant que l'utilisateur n'enverra pas deux nouvelles photos. En d'autres termes, l'utilisateur ne peut alors changer le prix du produit sans envoyer deux nouvelles photos. Les champs obligatoires du formulaire doivent être redéfinis afin d'inclure les suivants. Si l'utilisateur laisse vides tous les champs du formulaire ProductPhotoForm, ce formulaire sera alors complètement ignoré. Cependant, si au moins un champ possède des données (par exemple caption ou filename), le formulaire devra être validé et sauvegardé normalement. Pour ce faire, le formulaire a besoin d'une technique avancée nécessitant l'utilisation d'un post validateur personnalisé. La première étape consiste à modifier le formulaire ProductPhotoForm afin de rendre les champs caption et filename optionnels. // lib/form/doctrine/ProductPhotoForm.class.php public function configure() { $this->setValidator('filename', new sfValidatorFile(array( 'mime_types' => 'web_images', 'path' => sfConfig::get('sf_upload_dir').'/products', 'required' => false, ))); http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework $this->validatorSchema['caption']->setOption('required', false); } Dans le code ci-dessus, la valeur de l'option required a été modifiée à false, en surchargeant la valeur par défaut du validateur pour le champ filename. De plus, la valeur de l'option required du champ caption a été explicitement configurée à false. Le code ci-dessous se charge ensuite d'ajouter un post validateur à l'objet ProductPhotoCollectionForm. // lib/form/doctrine/ProductPhotoCollectionForm.class.php public function configure() { // ... $this->mergePostValidator(new ProductPhotoValidatorSchema()); } Un post validateur est un type de validateur particulier qui exécute une validation sur toutes les données soumises après le processus de validation classique. Il s'oppose à la validation valeur par valeur de chaque champ. L'un des post validateurs les plus courants est sfValidatorSchemaCompare qui vérifie, par exemple, que la valeur d'un certain champ est inférieure à celle d'un autre. Création d'un Validateur Personnalisé Heureusement, la création d'un validateur personnalisé est en fait simple. Il suffit de créer un nouveau fichier ProductPhotoValidatorSchema.class.php et de le placer dans le répertoire lib/validator. La création de ce répertoire est à la charge du lecteur. // lib/validator/ProductPhotoValidatorSchema.class.php class ProductPhotoValidatorSchema extends sfValidatorSchema { protected function configure($options = array(), $messages = array()) { $this->addMessage('caption', 'The caption is required.'); $this->addMessage('filename', 'The filename is required.'); } protected function doClean($values) { $errorSchema = new sfValidatorErrorSchema($this); foreach($values as $key => $value) { $errorSchemaLocal = new sfValidatorErrorSchema($this); // filename is filled but no caption if ($value['filename'] && !$value['caption']) { $errorSchemaLocal->addError(new sfValidatorError($this, 'required'), 'caption'); } // caption is filled but no filename if ($value['caption'] && !$value['filename']) { $errorSchemaLocal->addError(new sfValidatorError($this, 'required'), 'filename'); } // no caption and no filename, remove the empty values if (!$value['filename'] && !$value['caption']) { unset($values[$key]); } // some error for this embedded-form if (count($errorSchemaLocal)) { $errorSchema->addError($errorSchemaLocal, (string) $key); http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework } } // throws the error for the main form if (count($errorSchema)) { throw new sfValidatorErrorSchema($this, $errorSchema); } return $values; } } Tous les validateurs étendent la classe abstraite sfValidatorBase qui les oblige à implémenter la méthode doClean(), déclarée abstraite. La méthode configure() peut également être utilisée pour ajouter des options ou messages d'erreur au validateur. Dans l'exemple précédent, deux messages ont été ajoutés au validateur. D'autres options peuvent également être définies à l'aide de la méthode addOption(). La méthode doClean() est responsable du nettoyage et de la validation des valeurs envoyées. La logique du validateur est quant à elle triviale. Si une photo est envoyée uniquement avec un fichier ou une légende, alors une erreur est générée (sfValidatorErrorSchema) avec le message approprié ; Si une photo est soumise sans fichier et sans légende, alors les valeurs sont supprimées afin d'éviter de sauvegarder une photo vide ; Si aucune erreur de validation n'a été produite, la méthode retourne un tableau de valeurs nettoyées. Dans cette situation, étant donné que le validateur personnalisé doit être utilisé comme un validateur global, la méthode doClean() attend un tableau des valeurs soumises et retourne un tableau des valeurs nettoyées. Cependant les validateurs personnalisés peuvent être créés pour des champs individuels. Dans ce cas, la méthode doClean() n'attendra qu'une seule valeur (la valeur du champ) et ne retournera qu'une seule valeur nettoyée. La dernière étape consiste à surcharger la méthode saveEmbeddedForms() de la classe ProductForm afin de supprimer les formulaires de photos vides, et ainsi éviter de sauvegarder une photo vide en base de données. Une exception serait en effet levée car le champ caption est requis. public function saveEmbeddedForms($con = null, $forms = null) { if (null === $forms) { $photos = $this->getValue('newPhotos'); $forms = $this->embeddedForms; foreach ($this->embeddedForms['newPhotos'] as $name => $form) { if (!isset($photos[$name])) { unset($forms['newPhotos'][$name]); } } } return parent::saveEmbeddedForms($con, $forms); } Imbriquer Facilement des Formulaires Doctrine Une nouveauté de symfony 1.3 est la méthode sfFormDoctrine::embedRelation() qui offre au développeur la possibilité d'imbriquer automatiquement des relations n-à-plusieurs dans un formulaire. Dans l'exemple de ce chapitre, il serait alors intéressant de permettre à l'utilisateur de pouvoir à la fois télécharger deux nouvelles photos, mais aussi de le rendre capable de modifier les objets ProductPhoto existants rattachés à l'objet Product. Pour ce faire, il suffit d'utiliser la méthode embedRelation() afin d'ajouter un objet http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework ProductPhotoForm additionnel pour chaque objet ProductPhoto existant. // lib/form/doctrine/ProductForm.class.php public function configure() { // ... $this->embedRelation('Photos'); } En interne, sfFormDoctrine::embedRelation() fait quasiment la même chose que le processus décrit plus tôt pour imbriquer deux nouveaux objets ProductPhotoForm. Si deux relations ProductPhoto existent déjà, alors les objets widgetSchema et validatorSchema résultants seront de la forme suivante. widgetSchema ( [id] [name] [price] [newPhotos] [Photos] [0] [id] [caption] ), [1] [id] [caption] ), ), ) validatorSchema ( [id] [name] [price] [newPhotos] [Photos] [0] [id] [caption] ), [1] [id] [caption] ), ), ) => array => => => => => sfWidgetFormInputHidden, sfWidgetFormInputText, sfWidgetFormInputText, array(...) array( => array( => sfWidgetFormInputHidden, => sfWidgetFormInputText, => array( => sfWidgetFormInputHidden, => sfWidgetFormInputText, => array => => => => => sfValidatorDoctrineChoice, sfValidatorString, sfValidatorNumber, array(...) array( => array( => sfValidatorDoctrineChoice, => sfValidatorString, => array( => sfValidatorDoctrineChoice, => sfValidatorString, http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework L'étape qui suit consiste à ajouter du code dans la vue pour afficher les formulaires Photo imbriqués. // apps/frontend/module/product/templates/_form.php <?php foreach ($form['Photos'] as $photo): ?> <?php echo $photo['caption']->renderRow() ?> <?php echo $photo['filename']->renderRow(array('width' => 100)) ?> <?php endforeach; ?> Ce morceau de code est exactement le même que celui qui a été utilisé plus tôt pour embarquer les nouveaux formulaires de photos. Enfin, la dernière étape consiste à modifier le champ d'envoi de fichier par un widget qui permet à l'utilisateur de visualiser la photo courante et de la remplacer par une nouvelle (sfWidgetFormInputFileEditable). public function configure() { $this->useFields(array('filename', 'caption')); $this->setValidator('filename', new sfValidatorFile(array( 'mime_types' => 'web_images', 'path' => sfConfig::get('sf_upload_dir').'/products', 'required' => false, ))); $this->setWidget('filename', new sfWidgetFormInputFileEditable(array( 'file_src' => '/uploads/products/'.$this->getObject()->filename, 'edit_mode' => !$this->isNew(), 'is_image' => true, 'with_delete' => false, ))); $this->validatorSchema['caption']->setOption('required', false); } Les Evénements de Formulaire Une autre nouveauté de symfony 1.3 sont les évènements de formulaires qui peuvent être utilisés pour étendre n'importe quel objet de formulaire de n'importe où dans le code. Symfony propose les quatre évènements de formulaire suivants par défaut. form.post_configure est notifié après chaque configuration de formulaire ; form.filter_values filtre les paramètres fusionnés teintés et les tableaux de fichiers juste avant l'association avec le formulaire ; http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework form.validation_error est notifié dès que la validation du formulaire échoue ; form.method_not_found est notifié dès qu'une méthode inconnue est appelée. Enregistrement d'Erreurs Personnalisées via form.validation_error En utilisant les évènements de formulaires, il est possible d'ajouter des logs personnalisés pour les erreurs de validation sur tous les formulaires du projet. Ces outils peuvent s'avérer utiles pour identifier les champs des formulaires qui entraînent des conflits pour les utilisateurs. Pour ce faire, il convient d'enregistrer un nouvel écouteur à partir de l'expéditeur d'événements, event dispatcher, pour l'événement form.validation_error en ajoutant le code suivant à la méthode setup() de la classe ProjectConfiguration. Cette dernière se trouve à l'intérieur du répertoire config/ du projet. public function setup() { // ... $this->getEventDispatcher()->connect( 'form.validation_error', array('BaseForm', 'listenToValidationError') ); } La classe BaseForm, qui se trouve dans le répertoire lib/form, est une classe spéciale de formulaires dont toutes les autres classes de formulaire héritent. BaseForm est essentiellement une classe utilitaire servant à partager du code et de la logique métier communs à tous les objets de formulaire du projet. Pour activer le log des erreurs de validation, il suffit simplement d'ajouter le code suivant à la classe BaseForm. public static function listenToValidationError($event) { foreach ($event['error'] as $key => $error) { self::getEventDispatcher()->notify(new sfEvent( $event->getSubject(), 'application.log', array ( 'priority' => sfLogger::NOTICE, sprintf('Validation Error: %s: %s', $key, (string) $error) ) )); } } Styles Personnalisés des Erreurs d'un Champ de Formulaire En guise de dernier exercice, il s'agit d'aborder un sujet légèrement plus sobre concernant la personnalisation des éléments du formulaire. C'est tout à fait le cas, par exemple, lorsqu'il s'agit d'appliquer un style spécial au design de la page Product pour tous les champs du formulaire dont la validation a échoué. http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework Si l'on admet que le designer a déjà implémenté les feuilles de styles qui permettent d'appliquer un style d'erreur personnalisé à n'importe quel champ input dans une div avec la classe form_error_row. Comment ajouter simplement la classe form_row_error aux champs erronés ? La réponse se trouve dans un objet spécial appelé form schema formatter. Chaque formulaire symfony utilise un form schema formatter pour déterminer le code HTML adéquat à utiliser lors de l'affichage des éléments du formulaire. Par défaut, symfony utilise un formateur de formulaire qui s'appuie sur les balises HTML <table>. Tout d'abord, il s'agit de créer une nouvelle classe de formatage de formulaire qui utilise juste quelques balises pour l'affichage du formulaire. Pour ce faire, il convient de créer un nouveau fichier sfWidgetFormSchemaFormatterAc2009.class.php dans le répertoire lib/widget/. La création de ce dernier est à la charge du lecteur. class sfWidgetFormSchemaFormatterAc2009 extends sfWidgetFormSchemaFormatter { protected $rowFormat = "<div class="form_row"> %label% \n %error% <br/> %field% %help% %hidden_fields%\n</div>\n", $errorRowFormat = "<div>%errors%</div>", $helpFormat = '<div class="form_help">%help%</div>', $decoratorFormat = "<div>\n %content%</div>"; } Bien que le format de cette classe paraisse étrange, l'idée générale est que la méthode renderRow() fasse usage de la variable $rowFormat afin de procéder à l'affichage. Une classe de formatage de formulaire offre d'autres options de formatage qui ne sont pas détaillées ici. Pour plus d'informations à ce sujet, l'API de symfony 1.3 est disponible. Ajouter le code suivant à la classe ProjectConfiguration suffit à utiliser le nouveau formateur de formulaires dans tous les objets de formulaire du projet. class ProjectConfiguration extends sfProjectConfiguration { public function setup() { // ... sfWidgetFormSchema::setDefaultFormFormatterName('ac2009'); } } L'objectif est ici d'attribuer une classe form_row_error à l'élément div form_row seulement si un champ échoue à la validation. Pour ce faire, il suffit d'inclure un jeton %row_class% à la propriété $rowFormat, puis de surcharger la méthode sfWidgetFormSchemaFormatter::formatRow() comme suit. class sfWidgetFormSchemaFormatterAc2009 extends sfWidgetFormSchemaFormatter { protected $rowFormat = "<div class="form_row%row_class%"> %label% \n %error% <br/> %field% %help% %hidden_fields%\n</div>\n", // ... http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Formulaires Avancés | symfony | Web PHP Framework public function formatRow($label, $field, $errors = array(), $help = '', $hiddenFields = null) { $row = parent::formatRow( $label, $field, $errors, $help, $hiddenFields ); return strtr($row, array( '%row_class%' => (count($errors) > 0) ? ' form_row_error' : '', )); } } Avec ce code, chaque élément affiché via la méthode renderRow() sera automatiquement décoré d'une balise div avec une classe form_row_error si la validation du champ échoue. Conclusion Le framework de formulaires est à la fois le composant le plus puissant et le plus complexe de symfony. Le compromis pour une validation minutieuse, une protection CSRF, et les objets de formulaire peut très vite s'avérer être une tâche redoutable lorsqu'il s'agit d'étendre le framework. En revanche, la connaissance en profondeur du système de formulaires est la clé pour révéler tout son potentiel. Les développements futurs du framework de formulaires se focaliseront sur la conservation de la puissance de cet outil, et sur la réduction de la complexité en offrant plus de flexibilité au développeur. Le framework de formulaires n'en est finalement qu'à ses débuts... « Widgets et Validateurs Personnalisés Etendre la Web Debug Toolbar » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/06-Advanced-Forms[31/12/2010 02:33:56] The More with symfony book | Etendre la Web Debug Toolbar | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Etendre la Web Debug Toolbar You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Ryan Weaver La barre de débogage web, web debug toolbar (WDT), de symfony regroupe des outils qui permettent de déboguer et d'améliorer les performances d'une application. Elle est constituée d'outils appelés panneaux de débogage web, web debug panels, chacun donnant des informations relatives au cache, à la configuration, aux fichiers de logs, à la consommation mémoire, au temps d'exécution ou bien encore concernant la version de symfony. Symfony 1.3 introduit deux nouveaux panneaux, un pour la vue et l'autre pour la gestion des emails. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Depuis symfony 1.2 il est possible de créer et d'ajouter ses propres panneaux de débogage web. Tout au long de ce chapitre, il s'agira de créer un nouvel onglet de débogage en étudiant par la même occasion les outils et les options qui permettent de le personnaliser. Il est de plus possible de se référer au plugin ac2009WebDebugPlugin qui contient de nombreux panneaux supplémentaires s'appuyant sur les techniques expliquées dans la suite de ce chapitre. Créer un Nouveau Panneau de Débogage Web Les composants de la barre de débogage web sont appelés web debug panels, ou bien panneaux de débogage web pour les puristes francophones. Ils étendent la classe sfWebDebugPanel. Ajouter un nouveau panel est particulièrement simple puisqu'il s'agit tout d'abord de créer un nouveau fichier sfWebDebugPanelDocumentation.class.php dans le dossier lib/debug/ du projet. Le répertoire lib/debug doit être créé manuellement. // lib/debug/sfWebDebugPanelDocumentation.class.php class acWebDebugPanelDocumentation extends sfWebDebugPanel { public function getTitle() { return '<img src="/images/documentation.png" alt="Documentation Shortcuts" height="16" width="16" /> docs'; } public function getPanelTitle() { return 'Documentation'; } public function getPanelContent() { $content = 'Placeholder Panel Content'; return $content; } } Tous les panneaux doivent implémenter au minimum les méthodes getTitle(), getPanelTitle() et getPanelContent(). sfWebDebugPanel::getTitle() définit l'apparence du panneau dans la barre de débogage. Il s'agit généralement d'un nom court accompagné d'une icône. sfWebDebugPanel::getPanelTitle() définit le nom du panneau qui est affiché dans le tag h1 de l'onglet ouvert. Il sert aussi d'attribut title au lien de la barre de débogage. Cette méthode ne doit pas retourner de code HTML. sfWebDebugPanel::getPanelContent() génère le code HTML affiché lorsque l'onglet est http://www.symfony-project.org/more-with-symfony/1_4/fr/07-Extending-the-Web-Debug-Toolbar[31/12/2010 02:34:02] Chapter Content Créer un Nouveau Panneau de Débogage Web Les Trois Types de Panneaux de Débogage Web Le Type Icon-Only Le Type Link Le Type Content Personnaliser le Contenu d'un Onglet La Méthode sfWebDebugPanel::setStatus() La Méthode sfWebDebugPanel::getToggler() La Méthode sfWebDebugPanel::getToggleableDebugStack() La Méthode sfWebDebugPanel::formatFileLink() Autres Informations à Connaître au Sujet de la WDT Enlever les Onglets par Défaut Accéder aux Paramètres de la The More with symfony book | Etendre la Web Debug Toolbar | symfony | Web PHP Framework ouvert. Pour finir cette implémentation, il ne reste plus qu'à informer l'application que l'on souhaite inclure ce nouvel onglet à la barre de débogage. Pour ce faire, il est nécessaire d'ajouter un nouvel écouteur sur l'événement debug.web.load_panels. Cet événement est notifié lorsque la barre de débogage recherche ses panneaux. Il suffit donc de modifier le contenu du fichier config/ProjectConfiguration.class.php afin de lui faire implémenter cet écouteur. // config/ProjectConfiguration.class.php public function setup() { // ... $this->dispatcher->connect('debug.web.load_panels', array( 'acWebDebugPanelDocumentation', 'listenToLoadDebugWebPanelEvent' )); } L'étape suivante consiste à ajouter la méthode listenToLoadDebugWebPanelEvent() à la classe acWebDebugPanelDocumentation.class.php afin d'ajouter le nouvel onglet dans la base d'outils. Cette méthode sera appelée lorsque l'évènement debug.web.load_panels sera notifié. // lib/debug/sfWebDebugPanelDocumentation.class.php public static function listenToLoadDebugWebPanelEvent(sfEvent $event) { $event->getSubject()->setPanel( 'documentation', new self($event->getSubject()) ); } Voilà ! Il ne reste plus qu'à rafraîchir le navigateur et apprécier le résultat. Depuis symfony 1.3, un paramètre sfWebDebugPanel peut être ajouté à l'URL d'une page pour charger automatiquement un panneau de débogage. Si l'on ajoute par exemple la chaîne ?sfWebDebugPanel=documentation à la fin de l'URL, le nouveau panneau sera ajouté à la page. C'est particulièrement utile lorsqu'il s'agit de développer des panneaux personnalisés. Les Trois Types de Panneaux de Débogage Web En réalité, il existe trois différents types de panneaux de débogage web. Les lignes qui suivent les décrivent les uns après les autres afin de mieux comprendre les intérêts de chacun. Le Type Icon-Only Ce type de panneau se contente d'afficher une icône et du texte dans la barre d'outil. C'est typiquement le cas du panneau memory qui affiche la mémoire consommée sans aucun lien supplémentaire. Pour créer un panel de type icon-only, la méthode getPanelContent() doit retourner une chaine vide. La seule sortie de l'onglet provient en réalité de la méthode getTitle(). public function getTitle() { $totalMemory = sprintf('%.1f', (memory_get_peak_usage(true) / 1024)); return '<img src="'.$this->webDebug->getOption('image_root_path').'/memory.png" alt="Memory" /> '.$totalMemory.' KB'; } public function getPanelContent() { return; } Le Type Link Au même titre que le panneau de type Icon-Only, l'onglet de type link n'a pas de contenu, mais dispose cependant d'un lien supplémentaire. L'URL de ce lien est défini par la méthode getTitleUrl(). Pour créer un panneau de type link, la méthode getPanelContent() doit retourner une chaine http://www.symfony-project.org/more-with-symfony/1_4/fr/07-Extending-the-Web-Debug-Toolbar[31/12/2010 02:34:02] Requête depuis un Onglet Masquer le Panneau sous Certaines Conditions Conclusion Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Search powered by google The More with symfony book | Etendre la Web Debug Toolbar | symfony | Web PHP Framework vide tandis que la méthode getTitleUrl() doit être ajoutée à la classe comme le montre l'exemple ci-dessous. public function getTitleUrl() { // link to an external uri return 'http://www.symfony-project.org/api/1_3/'; // or link to a route in your application return url_for('homepage'); } public function getPanelContent() { return; } Le Type Content Enfin, le panneau de type content est le plus courant de tous puisqu'il s'agit d'un onglet qui affiche un bloc de code HTML lorsque l'on clique dessus dans la barre de débogage web. Pour créer ce type de panneau de contenu, la méthode getPanelContent() doit retourner autre chose qu'une chaine vide. Personnaliser le Contenu d'un Onglet Maintenant que le nouveau panneau de contrôle a été ajouté à la barre de débogage de symfony, il convient de lui attribuer du contenu à l'aide de la méthode getPannelContent(). Symfony fournit plusieurs méthodes capables de rendre ce contenu à la fois riche et ergonomique. La Méthode sfWebDebugPanel::setStatus() La couleur de fond des onglets est grise par défaut. Elle peut néanmoins être modifiée par de l'orange ou bien par du rouge lorsqu'il s'agit d'attirer l'attention du développeur sur un élément précis du panneau. Pour changer la couleur de fond du panel, la solution consiste à utiliser la méthode setStatus(). Cette méthode accepte toutes les constantes priority de la classe sfLogger. Il existe en particulier trois niveaux qui correspondent respectivement aux trois couleurs de fond que peut prendre le panel (gris, orange et rouge). La méthode setStatus() est généralement appelée depuis la méthode getPanelContent() si certains événements nécessitent d'attirer l'attention du développeur. public function getPanelContent() { // ... // set the background to gray (the default) $this->setStatus(sfLogger::INFO); // set the background to orange $this->setStatus(sfLogger::WARNING); // set the background to red $this->setStatus(sfLogger::ERR); } La Méthode sfWebDebugPanel::getToggler() Il existe une fonction fréquemment rencontrée dans les panneaux de contrôle. Il s'agit du toggler. C'est une croix qui affiche ou cache alternativement du contenu lorsque l'on clique dessus. On pourrait généraliser son comportement à celui d'un interrupteur domestique qui allume ou éteint une ampoule par exemple. Le toggler peut être utilisé par le panneau de contrôle en invoquant la méthode getToggler(). http://www.symfony-project.org/more-with-symfony/1_4/fr/07-Extending-the-Web-Debug-Toolbar[31/12/2010 02:34:02] The More with symfony book | Etendre la Web Debug Toolbar | symfony | Web PHP Framework Le code ci-dessous explique comment appliquer le toggler sur une liste d'éléments contenus dans l'onglet de contrôle. public function getPanelContent() { $listContent = '<ul id="debug_documentation_list" style="display: none;"> <li>List Item 1</li> <li>List Item 2</li> </ul>'; $toggler = $this->getToggler('debug_documentation_list', 'Toggle list'); return sprintf('<h3>List Items %s</h3>%s', $toggler, $listContent); } Cette méthode getToggler() accepte deux arguments : l'identifiant DOM, id, de l'élément sur lequel doit être appliqué le toggler, et une chaîne pour l'attribut title du lien du toggler. La Méthode sfWebDebugPanel::getToggleableDebugStack() Au même titre que la méthode getToggler(), la méthode getToggleableDebugStack() crée une flèche cliquable qui affiche ou masque un élément du contenu. Cette méthode génère le contenu HTML d'une trace de débogage d'une pile d'appels de fonctions. Cette fonction est particulièrement utile lorsqu'il s'agit d'afficher les logs d'une des classes du projet. Par exemple, si une classe myCustomClass a besoin d'enregistrer des informations dans les logs, alors ces derniers seraient créés de la manière suivante depuis l'intérieur de cette classe. class myCustomClass { public function doSomething() { $dispatcher = sfApplicationConfiguration::getActive() ->getEventDispatcher(); $dispatcher->notify(new sfEvent($this, 'application.log', array( 'priority' => sfLogger::INFO, 'Beginning execution of myCustomClass::doSomething()', ))); } } Pour l'exemple de ce chapitre, il s'agit d'afficher une liste des logs de la classe myCustomClass en accompagnant chacun d'eux par sa propre trace de débogage de la pile d'appels. public function getPanelContent() { // retrieves all of the log messages for the current request $logs = $this->webDebug->getLogger()->getLogs(); $logList = ''; foreach ($logs as $log) { if ($log['type'] == 'myCustomClass') { $logList .= sprintf('<li>%s %s</li>', $log['message'], $this->getToggleableDebugStack($log['debug_backtrace']) ); } } return sprintf('<ul>%s</ul>', $logList); } http://www.symfony-project.org/more-with-symfony/1_4/fr/07-Extending-the-Web-Debug-Toolbar[31/12/2010 02:34:02] The More with symfony book | Etendre la Web Debug Toolbar | symfony | Web PHP Framework Les logs de la classe myCustomClass se trouvent toujours dans le panneau Logs. Utiliser un onglet personnalisé permet ainsi de les isoler et de les présenter autrement. La Méthode sfWebDebugPanel::formatFileLink() Depuis symfony 1.3, il est possible d'ouvrir un fichier dans son éditeur favoris depuis la barre de débogage web. Davantage d'informations concernant cette fonctionnalité sont disponibles sur Internet, il suffit pour cela de se référer à l'article "What's new" de symfony 1.3. Pour bénéficier de cette fonctionnalité, il faut impérativement faire appel à la méthode formatFileLink(). En plus du nom du fichier à ouvrir, cette méthode peut aussi recevoir comme second paramètre le numéro de la ligne incriminée à atteindre. L'exemple suivant montre comment atteindre la ligne 15 du fichier config/ProjectConfiguration.class.php: public function getPanelContent() { $content = ''; // ... $path = sfConfig::get('sf_config_dir') . '/ProjectConfiguration.class.php'; $content .= $this->formatFileLink($path, 15, 'Project Configuration'); return $content; } Les deuxième et troisième arguments, sont respectivement le numéro de la ligne et le nom du lien, et sont également optionnels. Si le nom du lien n'est pas renseigné, c'est le chemin du fichier ciblé qui sera utilisé à la place. Avant de tester, il convient de s'assurer de bien avoir configuré la nouvelle fonctionnalité de lien de fichier. Cela peut être réalisé à l'aide de l'option sf_file_link_format dans le fichier settings.yml ou bien avec l'option file_link_format de xdebug. Cette dernière méthode permet aussi à votre projet de ne pas dépendre d'un IDE. Autres Informations à Connaître au Sujet de la WDT L'intérêt de la barre de débogage réside dans les informations qu'elle affiche. Il s'agit maintenant d'étudier les autres possibilités offertes par la barre de débogage web de symfony. Enlever les Onglets par Défaut La barre de débogage charge par défaut plusieurs panneaux qui peuvent être enlevés en utilisant l'événement debug.web.load_panels. Il s'agit ici de faire appel à la même méthode écouteur déclarée plus haut, en remplaçant son contenu par la méthode removePanel(). Le code suivant supprime l'onglet memory de la barre de débogage web. static public function listenToLoadDebugWebPanelEvent(sfEvent $event) { $event->getSubject()->removePanel('memory'); } Accéder aux Paramètres de la Requête depuis un Onglet Les paramètres de la requête sont souvent nécessaires dans les onglets. Il convient donc maintenant d'afficher des informations concernant un objet Event provenant de la base de données à partir d'un paramètre de requête event_id. $parameters = $this->webDebug->getOption('request_parameters'); http://www.symfony-project.org/more-with-symfony/1_4/fr/07-Extending-the-Web-Debug-Toolbar[31/12/2010 02:34:02] The More with symfony book | Etendre la Web Debug Toolbar | symfony | Web PHP Framework if (isset($parameters['event_id'])) { $event = Doctrine::getTable('Event')->find($parameters['event_id']); } Masquer le Panneau sous Certaines Conditions Parfois, l'onglet ne dispose pas d'informations utiles à afficher. Dans ce cas, il est préférable de le masquer. Pour mieux comprendre comment mettre cela en place, le code suivant s'appuiera sur l'exemple précédent. Il s'agit de masquer le panneau de contrôle si aucun paramètre event_id n'a été transmis dans la requête. Pour ce faire, la méthode getTitle() doit retourner une chaine vide. public function getTitle() { $parameters = $this->webDebug->getOption('request_parameters'); if (!isset($parameters['event_id'])) { return; } return '<img src="/acWebDebugPlugin/images/documentation.png" alt="Documentation Shortcuts" height="16" width="16" /> docs'; } Conclusion La barre de débogage de symfony est là pour faciliter la vie du développeur. Elle est aussi bien plus qu'un simple panneau d'informations passif puisqu'en lui ajoutant des onglets personnalisés, les fonctionnalités de la barre d'outils ne seront limitées que par l'imagination du développeur. Le plugin ac2009WebDebugPlugin ne contient qu'un aperçu des panneaux pouvant être créés, alors n'hésitez pas à ajouter les vôtres également. « Formulaires Avancés Techniques Avancées avec Doctrine » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/07-Extending-the-Web-Debug-Toolbar[31/12/2010 02:34:02] The More with symfony book | Techniques Avancées avec Doctrine | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Techniques Avancées avec Doctrine You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Jonathan H. Wage Ecrire un Comportement Doctrine L'objectif de ce chapitre est de découvrir comment écrire un comportement (behavior) pour Doctrine 1.2. Il s'agira de créer un exemple simple qui permet de maintenir à jour un compteur de relations en cache dans un champ d'une table. Cette fonctionnalité permettra ainsi d'éviter d'avoir à demander le résultat d'un dénombrement à chaque appel d'une méthode en réalisant des requêtes supplémentaires. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Ce type de fonctionnalité est relativement simple à mettre en oeuvre. Pour chaque relation dont on souhaite conserver à jour le résultat d'un compteur, le comportement se chargera d'ajouter une colonne supplémentaire au modèle afin de stocker la valeur courante du dénombrement. Le Schéma de Données Le listing ci-dessous décrit le modèle de données utilisé pour commencer. Puis, il sera modifié au fil du chapitre afin d'enregistrer le comportement développé dans la section actAs du modèle. # config/doctrine/schema.yml Thread: columns: title: type: string(255) notnull: true Post: columns: thread_id: type: integer notnull: true body: type: clob notnull: true relations: Thread: onDelete: CASCADE foreignAlias: Posts Le schéma de données établi, il ne reste plus qu'à construire tout le modèle de données associé à l'aide de la tâche doctrine:build. $ php symfony doctrine:build --all Le Template Doctrine La première étape consiste à écrire une classe basique qui étend la classe Doctrine_Template. Cette classe sera responsable de l'ajout de la colonne qui stocke la valeur courante du compteur dans la classe de modèle associée. Pour ce faire, il suffit de créer le fichier CountCache.class.php dans l'un des répertoires lib/ du projet afin que symfony puisse le charger automatiquement. // lib/count_cache/CountCache.class.php class CountCache extends Doctrine_Template { public function setTableDefinition() http://www.symfony-project.org/more-with-symfony/1_4/fr/08-Advanced-Doctrine-Usage[31/12/2010 02:34:05] Chapter Content Ecrire un Comportement Doctrine Le Schéma de Données Le Template Doctrine Ecouter les Evénements Tester le Comportement Utiliser le Cache des Résultats Doctrine Le Modèle de Données Configurer le Cache de Résultats Exemples de Requêtes Supprimer le Cache Supprimer des Entrées du Cache à l'Aide des Evénements Développer un Hydrator Doctrine Le Modèle de Données et les Données de Test Développer l'Hydrator The More with symfony book | Techniques Avancées avec Doctrine | symfony | Web PHP Framework { } Utiliser l'Hydrator public function setUp() { } } A présent, il est temps de modifier la définition du modèle Post afin que l'objet implémente (actAs) le comportement CountCache. # config/doctrine/schema.yml Post: actAs: CountCache: ~ # ... Le modèle Post est désormais prêt à utiliser le comportement CountCache, bien que quelques explications complémentaires à son sujet soient les bienvenues. Dès lors que la définition du modèle est instanciée, n'importe quel comportement attaché à ce dernier voit ses méthodes setTableDefinition() et setUp() invoquées comme celles qui se trouvent dans la classe BasePost du fichier lib/model/doctrine/base/BasePost.class.php. Ce mécanisme permet d'ajouter des choses supplémentaires à n'importe quel modèle à la manière "plug and play". Ces dernières peuvent être aussi bien des colonnes, des relations, des écouteurs d'évènements, etc. Le fonctionnement général des comportements a été éclairci. Par conséquent, il convient de faire en sorte que le comportement CountCache satisfasse réellement un besoin fonctionnel. class CountCache extends Doctrine_Template { protected $_options = array( 'relations' => array() ); public function setTableDefinition() { foreach ($this->_options['relations'] as $relation => $options) { // Build column name if one is not given if (!isset($options['columnName'])) { $this->_options['relations'][$relation]['columnName'] = 'num_'.Doctrine_Inflector::tableize($relation); } // Add the column to the related model $columnName = $this->_options['relations'][$relation]['columnName']; $relatedTable = $this->_table->getRelation($relation)->getTable(); $this->_options['relations'][$relation]['className'] = $relatedTable>getOption('name'); $relatedTable->setColumn($columnName, 'integer', null, array('default' => 0)); } } } Le code ci-dessus ajoute désormais des colonnes pour maintenir à jour le compteur du modèle associé. Ainsi, dans l'étude de cas courante, le comportement est attaché au modèle Post sur la relation Thread associée. L'objectif est de maintenir le nombre de messages (posts) quelle que soit l'instance de la classe Thread dans une colonne nommée num_posts. Le modèle de données YAML peut alors être modifié comme ci-après afin de définir l'option complémentaire du comportement. # ... Post: actAs: CountCache: relations: Thread: columnName: num_posts foreignAlias: Posts # ... http://www.symfony-project.org/more-with-symfony/1_4/fr/08-Advanced-Doctrine-Usage[31/12/2010 02:34:05] Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Search powered by google The More with symfony book | Techniques Avancées avec Doctrine | symfony | Web PHP Framework Désormais, le modèle Thread contient une colonne num_posts dont la valeur sera conservée à jour avec le nombre de messages que chaque sujet de discussion possède. Ecouter les Evénements L'étape suivante de la construction du comportement consiste à écrire un écouteur d'événements pour l'enregistrement en cours. Cet écouteur est responsable de la bonne conservation de la valeur du compteur lorsque de nouveaux enregistrements sont insérés en base de données, ou bien lorsqu'un (ou plusieurs) enregistrement(s) est (sont) supprimé(s en DQL). class CountCache extends Doctrine_Template { // ... public function setTableDefinition() { // ... $this->addListener(new CountCacheListener($this->_options)); } } Avant d'aller plus loin, la classe CountCacheListener doit être définie et étendre Doctrine_Record_Listener. Cette classe accepte un tableau d'options transmis simplement du template à l'écouteur. // lib/model/count_cache/CountCacheListener.class.php class CountCacheListener extends Doctrine_Record_Listener { protected $_options; public function __construct(array $options) { $this->_options = $options; } } A présent, les évènements suivants doivent être initialisés dans le but de garder les compteurs à jour en permanence. postInsert() incrémente le compteur lorsqu'un nouvel objet est inséré ; postDelete() décrémente le compteur lorsqu'un objet est supprimé ; preDqlDelete() décrémente les compteurs lorsque les enregistrements sont supprimés à partir d'un ordre DQL DELETE. Le listing ci-dessous définit tout d'abord la méthode postInsert() : class CountCacheListener extends Doctrine_Record_Listener { // ... public function postInsert(Doctrine_Event $event) { $invoker = $event->getInvoker(); foreach ($this->_options['relations'] as $relation => $options) { $table = Doctrine::getTable($options['className']); $relation = $table->getRelation($options['foreignAlias']); $table ->createQuery() ->update() ->set($options['columnName'], $options['columnName'].' + 1') ->where($relation['local'].' = ?', $invoker->$relation['foreign']) ->execute(); } } } Le code ci-dessus incrémente les compteurs d'une unité pour toutes les relations configurées à l'aide d'une requête DQL UPDATE à chaque fois qu'un objet similaire à celui ci-dessous est inséré. http://www.symfony-project.org/more-with-symfony/1_4/fr/08-Advanced-Doctrine-Usage[31/12/2010 02:34:05] The More with symfony book | Techniques Avancées avec Doctrine | symfony | Web PHP Framework $post = new Post(); $post->thread_id = 1; $post->body = 'body of the post'; $post->save(); Le Thread ayant 1 pour id verra sa colonne num_posts augmentée de 1. Les compteurs sont à présent bien incrémentés lorsque de nouveaux objets sont insérés. Il convient maintenant de gérer la décrémentation des compteurs lorsque les objets sont supprimés en implémentant la méthode postDelete() suivante. class CountCacheListener extends Doctrine_Record_Listener { // ... public function postDelete(Doctrine_Event $event) { $invoker = $event->getInvoker(); foreach ($this->_options['relations'] as $relation => $options) { $table = Doctrine::getTable($options['className']); $relation = $table->getRelation($options['foreignAlias']); $table ->createQuery() ->update() ->set($options['columnName'], $options['columnName'].' - 1') ->where($relation['local'].' = ?', $invoker->$relation['foreign']) ->execute(); } } } La méthode postDelete() ci-dessus est presque identique à la méthode postInsert() puisque la seule différence qui les oppose est la décrémentation de 1 de la colonne num_posts au lieu de l'incrémentation. Cela se traduit par le code ci-dessous si l'enregistrement $post sauvegardé précédemment est supprimé. $post->delete(); La dernière pièce du puzzle consiste à gérer la mise à jour des compteurs à l'aide d'une requête DQL DELETE lorsque plusieurs objets sont supprimés d'un coup. Pour résoudre ce problème, il suffit d'implémenter la méthode preDqlDelete(). class CountCacheListener extends Doctrine_Record_Listener { // ... public function preDqlDelete(Doctrine_Event $event) { foreach ($this->_options['relations'] as $relation => $options) { $table = Doctrine::getTable($options['className']); $relation = $table->getRelation($options['foreignAlias']); $q = clone $event->getQuery(); $q->select($relation['foreign']); $ids = $q->execute(array(), Doctrine::HYDRATE_NONE); foreach ($ids as $id) { $id = $id[0]; $table ->createQuery() ->update() ->set($options['columnName'], $options['columnName'].' - 1') ->where($relation['local'].' = ?', $id) ->execute(); } } } http://www.symfony-project.org/more-with-symfony/1_4/fr/08-Advanced-Doctrine-Usage[31/12/2010 02:34:05] The More with symfony book | Techniques Avancées avec Doctrine | symfony | Web PHP Framework } Le code ci-dessus clone la requête DQL DELETE et la transforme en un SELECT qui permet de retrouver la liste des IDs qui seront supprimés. Par conséquent, les compteurs peuvent être mis à jour en fonction des enregistrements supprimés. Le scénario suivant est à présent pris en charge et les compteurs seront décrémentés si le code suivant était exécuté. Doctrine::getTable('Post') ->createQuery() ->delete() ->where('id = ?', 1) ->execute(); Ou bien si plusieurs enregistrements devaient être supprimés, les compteurs seraient eux aussi correctement décrémentés. Doctrine::getTable('Post') ->createQuery() ->delete() ->where('body LIKE ?', '%cool%') ->execute(); L'invocation de la méthode preDqlDelete() est soumise à l'activation d'un attribut. Les DQL de rappel (DQL callbacks) sont désactivés par défaut car ils ont un coût supplémentaire sur les performances. Par conséquent, il doivent être explicitement activés afin de pouvoir les utiliser. $manager->setAttribute(Doctrine_Core::ATTR_USE_DQL_CALLBACKS, true); C'est tout ! Le comportement Doctrine est terminé, mais la dernière chose qui reste à faire consiste à le tester un peu. Tester le Comportement Le code est désormais implémenté et peut donc être testé avec quelques jeux de données de test. # data/fixtures/data.yml Thread: thread1: title: Test Thread Posts: post1: body: This is the body of my test thread post2: body: This is really cool post3: body: Ya it is pretty cool Il ne reste plus qu'à tout reconstruire et charger les données de test. $ php symfony doctrine:build --all --and-load Maintenant que l'ensemble est reconstruit et que les données de test sont chargées, un test peut être exécuté afin de s'assurer que les compteurs ont bien été mis à jour: $ php symfony doctrine:dql "FROM Thread t, t.Posts p" doctrine - executing: "FROM Thread t, t.Posts p" () doctrine id: '1' doctrine title: 'Test Thread' doctrine num_posts: '3' doctrine Posts: doctrine doctrine id: '1' doctrine thread_id: '1' doctrine body: 'This is the body of my test thread' doctrine doctrine id: '2' doctrine thread_id: '1' http://www.symfony-project.org/more-with-symfony/1_4/fr/08-Advanced-Doctrine-Usage[31/12/2010 02:34:05] The More with symfony book | Techniques Avancées avec Doctrine | symfony | Web PHP Framework doctrine doctrine doctrine doctrine doctrine - body: 'This is really cool' id: '3' thread_id: '1' body: 'Ya it is pretty cool' La colonne num_posts de la classe de modèle Thread dispose bien de la valeur trois. Si l'un des posts est amené à être supprimé avec le code suivant, alors il décrémentera automatiquement le compteur associé de l'objet Thread. $post = Doctrine_Core::getTable('Post')->find(1); $post->delete(); Le listing ci-dessous prouve que l'enregistrement est bien supprimé et que le compteur a bien été mis à jour. $ php symfony doctrine:dql "FROM Thread t, t.Posts p" doctrine - executing: "FROM Thread t, t.Posts p" () doctrine id: '1' doctrine title: 'Test Thread' doctrine num_posts: '2' doctrine Posts: doctrine doctrine id: '2' doctrine thread_id: '1' doctrine body: 'This is really cool' doctrine doctrine id: '3' doctrine thread_id: '1' doctrine body: 'Ya it is pretty cool' Cela fonctionne de la même manière si les deux enregistrements restants sont supprimés en même temps à l'aide d'une requête DQL. Doctrine_Core::getTable('Post') ->createQuery() ->delete() ->where('body LIKE ?', '%cool%') ->execute(); A présent, tous les posts associés au sujet de discussion ont été supprimés et la colonne num_posts devrait ainsi conserver la valeur zéro. $ php symfony doctrine:dql "FROM Thread t, t.Posts p" doctrine - executing: "FROM Thread t, t.Posts p" () doctrine id: '1' doctrine title: 'Test Thread' doctrine num_posts: '0' doctrine Posts: { } C'est tout! J'espère que cet article vous a été utile dans le sens où vous avez appris quelque chose de nouveau au sujet des comportements. De plus, j'espère que ce comportement vous sera également utile. Utiliser le Cache des Résultats Doctrine Dans la plupart des applications web à fort trafic, il est commun de cacher de l'information afin d'économiser des ressources CPU. Avec la dernière version de Doctrine 1.2, de nombreuses améliorations ont été réalisées au niveau du cache des jeux de résultats afin d'offrir au développeur davantage de contrôle. En effet, le développeur a désormais plus de contrôle lorsqu'il s'agit de supprimer des entrées du cache depuis les gestionnaires de cache. Autrefois, il était impossible de spécifier la clé de cache utilisée pour stocker une entrée dans le cache. Par conséquent, l'entrée cachée ne pouvait être véritablement identifiée en vue de la supprimer. Cette section présentera, à partir d'un exemple simple, comment utiliser le cache de jeux de résultats afin de mettre en cache toutes les requêtes relatives à l'utilisateur courant. Cette mise en cache sera réalisée de la même manière qu'en utilisant des événements afin de s'assurer qu'elles sont correctement nettoyées lorsque des données évoluent. Le Modèle de Données http://www.symfony-project.org/more-with-symfony/1_4/fr/08-Advanced-Doctrine-Usage[31/12/2010 02:34:05] The More with symfony book | Techniques Avancées avec Doctrine | symfony | Web PHP Framework Pour cet exemple, le schéma suivant est utilisé. # config/doctrine/schema.yml User: columns: username: type: string(255) notnull: true unique: true password: type: string(255) notnull: true Une fois le schéma recopié, l'ensemble du projet peut alors être reconstruit à l'aide de la commande suivante. $ php symfony doctrine:build --all Ceci étant fait, la classe User ci-après devrait avoir été générée par Doctrine. // lib/model/doctrine/User.class.php /** * User * * This class has been auto-generated by the Doctrine ORM Framework * * @package ##PACKAGE## * @subpackage ##SUBPACKAGE## * @author ##NAME## <##EMAIL##> * @version SVN: $Id: Builder.php 6508 2009-10-14 06:28:49Z jwage $ */ class User extends BaseUser { } Il est important de noter que cette classe accueillera du code supplémentaire plus loin dans cet article. Configurer le Cache de Résultats Afin de pouvoir mettre en oeuvre le cache de résultats, un gestionnaire de cache doit d'abord être configuré pour les requêtes utilisées. Cette étape se réalise très simplement en configurant l'attribut ATTR_RESULT_CACHE. Dans cet article, c'est le gestionnaire de cache APC qui a été retenu dans la mesure où c'est le meilleur choix pour l'environnement de production. Si APC n'est pas disponible sur le serveur de développement, alors celui-ci pourra aussi bien se contenter des pilotes Doctrine_Cache_Db ou bien Doctrine_Cache_Array pour des besoins de test. Cet attribut est configurable dans la classe de configuration du projet, ProjectConfiguration. Il suffit pour cela de déclarer une méthode configureDoctrine() comme expliqué ci-dessous. // config/ProjectConfiguration.class.php // ... class ProjectConfiguration extends sfProjectConfiguration { // ... public function configureDoctrine(Doctrine_Manager $manager) { $manager->setAttribute(Doctrine_Core::ATTR_RESULT_CACHE, new Doctrine_Cache_Apc()); } } Maintenant que le gestionnaire de cache des résultats est configuré, il peut désormais être testé pour cacher les jeux de résultats des requêtes exécutées. Exemples de Requêtes Supposons que l'application dispose d'un certain nombre de requêtes relatives à l'utilisateur courant, et qu'elles doivent être nettoyées à chaque fois que les données de l'utilisateur sont modifiées. Le code ci-dessous présente une requête simple qui pourrait servir à rendre la liste http://www.symfony-project.org/more-with-symfony/1_4/fr/08-Advanced-Doctrine-Usage[31/12/2010 02:34:05] The More with symfony book | Techniques Avancées avec Doctrine | symfony | Web PHP Framework des utilisateurs triés par ordre alphabétique. $q = Doctrine_Core::getTable('User') ->createQuery('u') ->orderBy('u.username ASC'); A présent, le cache peut être activé pour cette requête en utilisant la méthode useResultCache. $q->useResultCache(true, 3600, 'users_index'); Notez le troisième argument. Il s'agit de la clé qui sera utilisée pour stocker l'entrée de cache des résultats dans le gestionnaire de cache. Cela permet ainsi d'identifier clairement cette requête afin de la supprimer du gestionnaire de cache. Désormais, lorsque la requête est exécutée, elle interroge tout d'abord la base de données pour obtenir les résultats. Puis, elle stocke ces derniers dans le gestionnaire de cache à la clé users_index, et ainsi, toutes les requêtes ultérieures iront chercher l'information dans le gestionnaire au lieu d'attaquer directement la base de données: $users = $q->execute(); Non seulement ce système fait économiser du traitement au serveur de base de données, il contourne également le processus complet d'hydratation puisque Doctrine sauvegarde les données déjà hydratées. Cela signifie que le serveur web en sera d'autant plus soulagé. A présent, si l'on contrôle le gestionnaire de cache, on découvrira une entrée nommée users_index. if ($cacheDriver->contains('users_index')) { echo 'cache exists'; } else { echo 'cache does not exist'; } Supprimer le Cache A ce stade, la requête est cachée, et il est temps d'en apprendre un peu plus au sujet de la suppression du cache. Le cache peut être supprimé manuellement en utilisant l'API du gestionnaire de cache ou bien en invoquant quelques événements pour nettoyer l'entrée de cache automatiquement lorsqu'un utilisateur est ajouté ou modifié. L'API du Gestionnaire de Cache Tout d'abord, il s'agit de présenter l'API brute du gestionnaire de cache avant de lui faire implémenter un nouvel événement. Pour avoir accès à l'instance du gestionnaire de cache des résultats, il suffit de faire appel à l'instance de la classe Doctrine_Manager. $cacheDriver = $manager->getAttribute(Doctrine_Core::ATTR_RESULT_CACHE); Si l'accès à la variable $manager est impossible, l'instance reste disponible à l'aide du code suivant: $manager = Doctrine_Manager::getInstance(); Il ne reste alors plus qu'à utiliser l'API du gestionnaire de cache pour supprimer des entrées du cache. $cacheDriver->delete('users_index'); Cependant, il est probable qu'il y ait plus d'une requête préfixée par users_, c'est pourquoi il convient de supprimer le cache de résultats pour toutes ces requêtes. Dans cet exemple, la méthode delete() actuelle ne fonctionnera pas. Par résoudre ce problème, Doctrine fournit une méthode nommée deleteByPrefix() qui supprime n'importe quelle entrée du cache qui http://www.symfony-project.org/more-with-symfony/1_4/fr/08-Advanced-Doctrine-Usage[31/12/2010 02:34:05] The More with symfony book | Techniques Avancées avec Doctrine | symfony | Web PHP Framework contient le préfixe passé en paramètre comme le montre l'exemple suivant. $cacheDriver->deleteByPrefix('users_'); Il existe d'autres méthodes très utiles facilitant la suppression des entrées du cache si la méthode deleteByPrefix() ne suffit pas à elle-même. deleteBySuffix($suffix) supprime les entrées du cache enregistrées avec le suffixe passé en paramètre ; deleteByRegex($regex) supprime les entrées du cache qui correspondent à l'expression régulière passée en paramètre ; deleteAll() supprime toutes les entrées du cache. Supprimer des Entrées du Cache à l'Aide des Evénements La méthode idéale pour nettoyer le cache serait de le faire à chaque fois que les données de l'utilisateur sont modifiées. Pour y parvenir, il suffit d'implémenter un événement postSave() dans la classe de définition du modèle User. Souvenez-vous de la classe User déclarée au tout début de ce chapitre. L'étape suivante consiste à lui implémenter la méthode postSave() ci-dessous afin de régénérer le cache des résultats à chaque fois que l'objet est modifié. // lib/model/doctrine/User.class.php class User extends BaseUser { // ... public function postSave($event) { $cacheDriver = $this->getTable()->getAttribute(Doctrine_Core::ATTR_RESULT_CACHE); $cacheDriver->deleteByPrefix('users_'); } } Grâce à ces quelques lignes, le cache des requêtes spécifiques à l'utilisateur sera nettoyé à chaque fois que ce dernier sera mis à jour ou ajouté dans la base de données. $user = new User(); $user->username = 'jwage'; $user->password = 'changeme'; $user->save(); La prochaine fois que les requêtes seront exécutées, Doctrine se chargera de récupérer les données à jour en provenance de la base de données dans la mesure où le cache n'existe pas encore. Ce n'est qu'après la récupération des enregistrements que ces derniers seront mis en cache pour toutes les requêtes ultérieures. Développer un Hydrator Doctrine L'une des principales fonctionnalités de Doctrine est sa capacité à transformer un objet Doctrine_Query en différents types de jeux de résultats. Cette transformation est assurée par les hydrators Doctrine. Jusqu'à Doctrine 1.2, les hydrators étaient codés en dur et figés, ce qui empêchait les développeurs d'écrire et d'utiliser les leurs. Heureusement, cette contrainte n'est plus et il désormais possible d'écrire des hydrators personnalisés. Par conséquent, n'importe quelle structure de données peut être créée afin de formater les résultats de la base de données lorsque une instance de la classe Doctrine_Query est exécutée. L'exemple présenté plus loin explique comment développer un hydrator à la fois simple et facile à comprendre, mais s'avère néanmoins très utile. Cet objet permettra de sélectionner deux valeurs et d'hydrater les données dans un tableau associatif dont la première colonne sera la clé et la seconde la valeur. Le Modèle de Données et les Données de Test Avant de débuter, il est nécessaire d'avoir un modèle de données épuré avec lequel seront exécutés quelques tests. Pour y parvenir, un simple modèle User suffit comme le présente le listing ci-dessous. # config/doctrine/schema.yml http://www.symfony-project.org/more-with-symfony/1_4/fr/08-Advanced-Doctrine-Usage[31/12/2010 02:34:05] The More with symfony book | Techniques Avancées avec Doctrine | symfony | Web PHP Framework User: columns: username: string(255) is_active: string(255) Afin de pouvoir tester le fonctionnement de l'hydrator, le modèle User doit disposer de quelques jeux de tests sommaires. Le listing ci-dessous fait état de deux objets User. # data/fixtures/data.yml User: user1: username: jwage password: changeme is_active: 1 user2: username: jonwage password: changeme is_active: 0 Ces données de tests peuvent désormais être chargées dans la base de données à l'aide de la commande suivante. $ php symfony doctrine:build --all --and-load Développer l'Hydrator L'écriture d'un hydrator personnalisé nécessite de déclarer une nouvelle classe dérivée de la classe abstraite Doctrine_Hydrator_Abstract, puis d'implémenter une méthode hydrateResultSet($stmt). Cette méthode reçoit en argument une instance de la classe PDOStatement utilisée pour exécuter la requête SQL. Par conséquent, cet objet peut être utilisé pour obtenir les résultats bruts de la requête grâce à PDO, puis de les transformer en une structure personnalisée. Pour y parvenir, il suffit de créer une nouvelle classe KeyValuePairHydrator dans le répertoire lib du projet afin que symfony puisse la charger automatiquement. // lib/KeyValuePairHydrator.class.php class KeyValuePairHydrator extends Doctrine_Hydrator_Abstract { public function hydrateResultSet($stmt) { return $stmt->fetchAll(Doctrine_Core::FETCH_NUM); } } En l'état, le code ci-dessus retourne les données brutes grâce à PDO, ce qui ne correspond pas vraiment aux spécifications techniques. Il s'agit donc de transformer ces données en une structure personnalisée de paires clé => valeur. Un modification mineure de la méthode hydrateResultSet() permet d'y parvenir. // lib/KeyValuePairHydrator.class.php class KeyValuePairHydrator extends Doctrine_Hydrator_Abstract { public function hydrateResultSet($stmt) { $results = $stmt->fetchAll(Doctrine_Core::FETCH_NUM); $array = array(); foreach ($results as $result) { $array[$result[0]] = $result[1]; } return $array; } } Ce fut facile, n'est-ce pas ? Le code de l'objet hydrator est désormais terminé et il répond parfaitement aux besoins. Il ne reste donc plus qu'à le tester pour s'assurer qu'il fonctionne correctement. Utiliser l'Hydrator Pour utiliser et tester l'hydrator, il est impératif de l'enregistrer afin que Doctrine ait http://www.symfony-project.org/more-with-symfony/1_4/fr/08-Advanced-Doctrine-Usage[31/12/2010 02:34:05] The More with symfony book | Techniques Avancées avec Doctrine | symfony | Web PHP Framework connaissance de la classe d'hydrator précédemment écrite lorsque les requêtes sont exécutées. Pour y parvenir, elle doit être enregistrée grâce à l'instance Doctrine_Manager depuis la classe ProjectConfiguration. // config/ProjectConfiguration.class.php // ... class ProjectConfiguration extends sfProjectConfiguration { // ... public function configureDoctrine(Doctrine_Manager $manager) { $manager->registerHydrator('key_value_pair', 'KeyValuePairHydrator'); } } L'hydrator est à présent enregistré et peut être utilisé avec des instances de la classe Doctrine_Query comme le montre l'exemple ci-dessous. $q = Doctrine_Core::getTable('User') ->createQuery('u') ->select('u.username, u.is_active'); $results = $q->execute(array(), 'key_value_pair'); print_r($results); L'exécution de ce code avec les jeux de données de tests définis plus haut provoque le résultat suivant. Array ( [jwage] => 1 [jonwage] => 0 ) Il n'en faut pas plus pour réaliser un hydrator aussi simplement. J'espère donc qu'il vous sera utile et que la communauté n'hésitera pas à contribuer en retour en développant de nouveaux hydrators pour Doctrine. « Etendre la Web Debug Toolbar Tirer Profit de l'Héritage de Table avec Doctrine » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/08-Advanced-Doctrine-Usage[31/12/2010 02:34:05] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Tirer Profit de l'Héritage de Table avec Doctrine You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Hugo Hamon Doctrine est officiellement devenu la bibliothèque d'ORM par défaut à partir de symfony 1.3 alors que le développement de Propel avait ralenti depuis quelques mois. Le projet Propel reste toutefois supporté dans symfony et continue de s'améliorer grâce notamment aux efforts des membres de la communauté symfony. Le projet Doctrine 1.2 est devenu le nouvel ORM par défaut de symfony pour deux raisons principales. En effet, Doctrine est beaucoup plus simple à utiliser que Propel et parce qu'il fournit de nombreuses fonctionnalités intéressantes telles que les comportements (behaviors), les requêtes DQL simplifiées, les migrations ou bien l'héritage de table. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Ce chapitre explique ce qu'est l'héritage de table et comment cette fonctionnalité est désormais parfaitement intégrée dans symfony 1.3. C'est à l'aide d'exemples concrets réels que ce chapitre illustrera comment tirer parti de l'héritage de table Doctrine afin de rendre le code plus flexible et mieux organisé. L'Héritage de Table Doctrine Bien qu'il ne soit pas encore très connu des développeurs et peu utilisé, l'héritage de table est probablement l'une des fonctionnalités les plus intéressantes de Doctrine. L'héritage de table permet au développeur de créer des tables, dans une base de données, qui héritent d'autres plus génériques de la même manière que des classes en étendent d'autres dans un langage de programmation orienté objet. En effet, cette fonctionnalité offre une manière simple et efficace pour partager des données entre deux ou plusieurs tables dans une même table maîtresse plus générique. Le diagramme ci-dessous explique ce principe de l'héritage de table. Chapter Content L'Héritage de Table Doctrine La Stratégie de l'Héritage Simple La Stratégie de l'Héritage de Table par Agrégation de Colonnes Doctrine intègre trois stratégies différentes pour gérer les héritages de table selon les besoins de l'applications en terme de performance, d'atomicité, d'efficacité ou bien encore de simplicité, etc. Ces trois stratégies natives sont l'héritage simple, l'héritage par agrégation de colonnes et l'héritage concret. Bien que que toutes ces stratégies soient présentées dans le livre Doctrine, des informations complémentaires aideront à mieux comprendre chacune de leurs options et dans quelles circonstances elles sont particulièrement utiles. La Stratégie de l'Héritage Simple La stratégie de l'héritage simple est, comme son nom l'indique, la plus simple de toutes dans la mesure où elle stocke toutes les colonnes, y compris celles des tables filles, dans la table maîtresse. Par exemple, si le schéma descriptif du modèle de données ressemble au code YAML http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] La Stratégie de l'Héritage de Table Concret Intégration de l'Héritage de Table avec Symfony Introduction aux Etudes de Cas Concrètes Héritage de Table au Niveau du Modèle Héritage de Table au Niveau des Formulaires L'Héritage de Table au Niveau des Filtres L'Héritage de Table avec l'Admin Generator The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework ci-après, alors Doctrine génèrera une seule table Person, dans laquelle seront fusionnées les colonnes des tables Professor et Student. Person: columns: first_name: type: notnull: last_name: type: notnull: Professor: inheritance: type: extends: columns: specialty: type: notnull: Student: inheritance: type: extends: columns: graduation: type: notnull: promotion: type: notnull: Conclusion Be trained by symfony experts string(50) true Jan 24: Paris string(50) true - Français) (Maîtrise de & Doctrine - Français) Feb 21: Paris Mar 21: Paris (Maîtrise de & Doctrine (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) simple Person May 23: Paris (Maîtrise de & Doctrine - Français) and more... string(50) true Search powered by google simple Person string(20) true integer(4) true Avec l'héritage de table simple, toutes les colonnes supplémentaires (specialty, graduation et promotion) sont automatiquement remontées au niveau supérieur dans le modèle Person, bien que Doctrine génère une classe de de modèle pour chacune des tables Student et Person. Cette stratégie a un inconvénient majeur dans la mesure où la table maîtresse Person ne fournit aucune colonne pour identifier le type de chaque enregistrement. En d'autres termes, il n'y a absolument aucun moyen de retrouver des objets de type Professor ou Student distincts. Par conséquent, l'exécution du code Doctrine ci-dessous retourne un objet Doctrine_Collection qui contient tous les enregistrements de la table (Student et Professor ensemble). $professors = Doctrine_Core::getTable('Professor')->findAll(); Il en résulte que la stratégie par héritage de table simple ne s'avère pas pratique pour des cas concrets d'application. En effet, la plupart des applications requièrent de sélectionner et d'hydrater des objets de type spécifiques. Par conséquent, cette stratégie est d'ores et déjà abandonnée pour la suite de ce chapitre. La Stratégie de l'Héritage de Table par Agrégation de Colonnes La stratégie de l'héritage par agrégation de colonnes est similaire à la stratégie d'héritage simple à la différence qu'elle inclut automatiquement une colonne type pour identifier les différents types d'enregistrements. Par conséquent, lorsqu'un un enregistrement est persisté en base de données, une valeur est affectée à cette colonne afin de déterminer à quelle classe il appartient. Person: columns: first_name: http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework type: notnull: last_name: type: notnull: Professor: inheritance: type: extends: keyField: keyValue: columns: specialty: type: notnull: Student: inheritance: type: extends: keyField: keyValue: columns: graduation: type: notnull: promotion: type: notnull: string(50) true string(50) true column_aggregation Person type 1 string(50) true column_aggregation Person type 2 string(20) true integer(4) true Dans le schéma de données ci-dessus, la stratégie d'héritage a été changée en column_aggregation et deux nouveaux attributs ont aussi fait leur apparition. Le premier, keyField, indique le nom de la colonne qui doit être créée pour stocker l'information concernant le type de l'enregistrement. L'attribut keyField est un entier obligatoire nommé type et est aussi le nom de la colonne par défaut s'il n'est pas explicitement spécifié dans le schéma de données. Le second attribut définit quant à lui la valeur à affecter pour chaque enregistrement qui appartient aux classes Professor et Student. La stratégie de l'agrégation de colonnes est une bonne méthode d'héritage de table dans la mesure où elle ne crée finalement qu'une seule table (Person) contenant tous les champs fusionnés, auxquels s'ajoute le champ type. De cette manière, il n'y a pas besoin de créer plusieurs tables et de les joindre par des requêtes SQL lorsqu'il s'agit de récupérer des données. Le listing ci-dessous montre quelques exemples d'interrogation des tables, ainsi que les types de résultats retournés. // Returns a Doctrine_Collection of Professor objects $professors = Doctrine_Core::getTable('Professor')->findAll(); // Returns a Doctrine_Collection of Student objects $students = Doctrine_Core::getTable('Student')->findAll(); // Returns a Professor object $professor = Doctrine_Core::getTable('Professor')->findOneBySpeciality('physics'); // Returns a Student object $student = Doctrine_Core::getTable('Student')->find(42); http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework // Returns a Student object $student = Doctrine_Core::getTable('Person')->findOneByIdAndType(array(42, 2)); Lorsqu'il s'agit de récupérer des données depuis une classe fille (Professor ou Student), Doctrine se charge d'ajouter automatiquement la clause WHERE à la requête avec la bonne valeur de filtre pour la colonne type. Toutefois, il existe quelques inconvénients quant à l'usage de la stratégie par agrégation de colonnes dans certains cas. Tout d'abord, l'agrégation de colonnes empêche tous les champs de chaque table fille d'être définis comme obligatoires. Par conséquent, selon le nombre de champs définis, la table Person pourra contenir des enregistrements pour lesquels plusieurs champs resteront vides. Le second inconvénient réside quant à lui dans le nombre de tables et de champs dérivés. Si le schéma de données déclare un nombre important de tables filles, qui elles mêmes définissent beaucoup de champs, alors il en résultera que la table maîtresse sera un large jeu de colonnes. Par conséquent, cette dernière risque de devenir moins performante et plus difficile à maintenir. La Stratégie de l'Héritage de Table Concret La stratégie de l'héritage de table concret est un bon compromis entre les avantages de la stratégie par agrégation de colonnes, les performances et la maintenabilité. En effet, cette stratégie crée des tables indépendantes pour chaque table fille, et réplique toutes les colonnes (colonnes partagées et colonnes spécifiques) à l'intérieur de celles-ci. Person: columns: first_name: type: notnull: last_name: type: notnull: Professor: inheritance: type: extends: columns: specialty: type: notnull: Student: inheritance: type: extends: columns: graduation: type: notnull: promotion: type: notnull: string(50) true string(50) true concrete Person string(50) true concrete Person string(20) true integer(4) true Ainsi, pour le schéma de données précédent, la table Professor générée contiendra l'ensemble des champs suivants : id, first_name, last_name et specialty. http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework Cette approche a de nombreux avantages par rapport aux stratégies précédentes. La première, c'est que toutes les tables sont désormais isolées et demeurent indépendantes les unes par rapport aux autres. De plus, cela a pour effet immédiat d'éliminer tous les champs vides ainsi que la colonne additionnelle type. De ce fait, chaque table est désormais plus légère et isolée des autres. Le fait que les champs partagés soient dupliqués dans les tables dérivées est un gain en termes de performance et de scalabilité. En effet, Doctrine n'a plus besoin de créer des jointures automatiques vers la table maîtresse lorsqu'il s'agit de récupérer des informations partagées par les enregistrements des tables filles. Les deux seuls inconvénients notables de la stratégie d'héritage concret sont la duplication des champs (bien que la réplication est généralement la clé vers les performances) et le fait que la table maîtresse générée demeurera toujours vide. En effet, Doctrine a généré une table Person alors qu'elle ne sera jamais remplie ni référencée par aucune requête SQL. Aucune requête ne sera exécutée sur cette table dans la mesure où toute l'information est sauvegardée dans les tables dérivées. Cette première partie du chapitre a permis de prendre le temps nécessaire pour introduire les trois types de stratégies d'héritage de table avec Doctrine. Cependant, il n'a pas encore été question de les mettre en pratique avec symfony dans des cas concrets issus de problématiques réelles. La section suivante du chapitre explique et présente comment profiter de la puissance de l'héritage de table Doctrine dans symfony 1.3, particulièrement au niveau du modèle et du framework de formulaires. Intégration de l'Héritage de Table avec Symfony Avant symfony 1.3, l'héritage de table Doctrine n'était pas complètement supporté par le framework dans la mesure où les classes de formulaires et de filtres n'héritaient pas de leur classe mère respective. Par conséquent, les développeurs qui avaient besoin d'utiliser l'héritage de table dans leurs projets, étaient contraints de réajuster les formulaires et les filtres. Ils étaient également forcés de redéfinir de nombreuses méthodes pour parvenir à reproduire le comportement de l'héritage, au détriment d'un temps perdu non négligeable... Heureusement, grâce aux retours d'utilisation de la communauté, l'équipe de développement de symfony a pu améliorer les classes de formulaires et de filtres dans le but de supporter facilement et entièrement l'héritage de table dans symfony 1.3. Le reste de ce chapitre explique comment utiliser l'héritage de table de Doctrine et comment en profiter dans plusieurs situations à travers le modèle, les formulaires, les filtres et le générateur d'administration. Pour ce faire, des exemples issus de problématiques réelles aideront à mieux comprendre comment l'héritage fonctionne avec symfony, afin de pouvoir facilement l'utiliser pour vos propres besoins. Introduction aux Etudes de Cas Concrètes Tout au long de ce chapitre, plusieurs cas concrets seront étudiés afin de dévoiler les principaux avantages de l'héritage de table Doctrine à plusieurs niveaux : modèle, formulaires, filtres et générateur d'administration. http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework Le premier exemple est tiré d'une application réelle développée chez Sensio pour un client grand compte français. Il explique en quoi l'héritage de table Doctrine est une excellente solution pour gérer une douzaine de jeux de référentiels de données identiques qui partagent des propriétés et méthodes similaires. L'objectif consistera ici à éviter la duplication du code partagé. Le deuxième exemple explique comment tirer profit de la stratégie de l'héritage de table concret au niveau des formulaires en créant un modèle de données simple destiné à la gestion de fichiers numériques. Enfin, le troisième et dernier exemple montrera comment tirer parti de l'héritage de table avec l'Admin Generator et comment le rendre plus flexible. Héritage de Table au Niveau du Modèle Comme avec la programmation orientée objet, l'héritage de table favorise le partage de données. En effet, l'héritage de table permet de partager des propriétés et des méthodes lorsqu'il s'agit de travailler avec des modèles générés. L'héritage de table de Doctrine est un bon moyen de partager et de redéfinir des actions propres aux objets hérités. La section suivante explique ce concept à l'aide d'un exemple pratique concret. La Problématique De nombreuses applications web fonctionnent à l'aide de jeux de données particuliers, appelés "référentiels". Un référentiel est généralement un jeu de données représenté par une simple table contenant au moins deux champs : id et label. Cependant, dans certains cas, le référentiel contient davantage d'informations comme par exemple des drapeaux is_active ou is_default. Ce fut exactement le cas chez Sensio pour un projet client. Le client souhaitait pouvoir gérer de multiples jeux de données servant à alimenter les formulaires et les vues majeures de l'application. Pour ce projet, toutes les tables de référentiels ont été construites selon le même modèle de base et incluent toutes par extension les colonnes suivantes : id, label, position et is_default. Le champ position sert ici à ordonner les enregistrements les uns par rapport aux autres grâce à un comportement Ajax de glisser / déposer (drag and drop). Le champ is_default quant à lui représente un marqueur qui indique si oui ou non l'enregistrement doit être sélectionné par défaut lorsqu'il alimente une liste déroulante HTML. La Solution Gérer de manière équivalente plus de deux tables similaires est l'une des meilleures problématiques solutionnables grâce à l'héritage de table. Pour résoudre la problématique expliquée précédemment, c'est la stratégie de l'héritage de table concret qui a été retenue afin de satisfaire les besoins fonctionnels de l'application, et partager les méthodes communes des objets dans la même classe de base. Le code YAML ci-dessous décrit un modèle de données simplifié pour illustrer la problématique. sfReferential: columns: id: type: integer(2) notnull: true label: type: string(45) notnull: true position: type: integer(2) notnull: true is_default: type: boolean notnull: true default: false sfReferentialContractType: inheritance: type: concrete extends: sfReferential sfReferentialProductType: inheritance: type: concrete extends: sfReferential http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework L'héritage de table concret fonctionne parfaitement dans ce cas dans la mesure où il génère des tables séparées et isolées les unes des autres. C'est d'autant plus important ici du fait du l'existence du champ position qui doit impérativement être géré pour des enregistrements de même type. Dès lors que le modèle de données est établi, l'étape suivante consiste à générer les classes de modèle correspondantes avant de les étudier. Pour ce schéma, Doctrine et symfony génèrent trois tables SQL distinctes et six classes de modèle dans le répertoire lib/model/doctrine: sfReferential gère les enregistrements de la table sf_referential ; sfReferentialTable gère la table sf_referential ; sfReferentialContractType gère sf_referential_contract_type ; les enregistrements de la table sfReferentialContractTypeTable gère la table sf_referential_contract_type ; sfReferentialProductType gère sf_referential_product_type ; les enregistrements de la table sfReferentialProductTypeTable gère la table sf_referential_product_type. Etudier les classes générées et leur héritage révèle que les deux classes de base des modèles sfReferentialContractType et sfReferentialProductType héritent de la classe sfReferential. Ainsi, toutes les méthodes publiques et protégées (y compris les propriétés) définies dans la classe sfReferential seront partagées jusque dans les classes les plus basses, et pourront être redéfinies si nécessaire. C'est exactement le but recherché et la classe sfReferential peut désormais encapsuler des méthodes pour gérer toutes les données des référentiels. Par exemple : // lib/model/doctrine/sfReferential.class.php class sfReferential extends BasesfReferential { public function promote() { // move up the record in the list } public function demote() { // move down the record in the list } public function moveToFirstPosition() { // move the record to the first position } public function moveToLastPosition() { // move the record to the last position } public function moveToPosition($position) { // move the record to a given position } public function makeDefault($forceSave = true, $conn = null) { $this->setIsDefault(true); if ($forceSave) { $this->save($conn); } } } Grâce à l'héritage de table concret de Doctrine, tout le code est partagé à la même place. Le code devient ainsi plus facile à déboguer, à maintenir, à faire évoluer et à tester unitairement. C'est le principal réel avantage de l'héritage de table. De plus, grâce à cette approche, les objets de modèle peuvent être utilisés pour centraliser le code des actions dans une classe spécifique. http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework La classe sfBaseReferentialActions ci-dessous est une classe générique d'actions, dérivée par chaque contrôleur de chaque module de gestion des référentiels. // lib/actions/sfBaseReferentialActions.class.php class sfBaseReferentialActions extends sfActions { /** * Ajax action that saves the new position as a result of the user * using a drag and drop in the list view. * * This action is linked thanks to an ~sfDoctrineRoute~ that * eases single referential object retrieval. * * @param sfWebRequest $request */ public function executeMoveToPosition(sfWebRequest $request) { $this->forward404Unless($request->isXmlHttpRequest()); $referential = $this->getRoute()->getObject(); $referential->moveToPosition($request->getParameter('position', 1)); return sfView::NONE; } } Que se passerait-il si le schéma de données n'utilisait pas de l'héritage de table ? Le code aurait alors besoin d'être dupliqué dans chaque classe de modèle de référentiel. Cette approche ne serait pas non plus très "DRY" (Don't Repeat Yourself), particulièrement lorsqu'il s'agit de travailler avec une douzaine de tables de référentiels identiques. Héritage de Table au Niveau des Formulaires Le visite guidée des avantages de l'héritage de table Doctrine continue. La section précédente a montré combien cette fonctionnalité pouvait s'avérer utile pour partager des méthodes et des propriétés communes entre plusieurs modèles. La suite de ce chapitre explique comment l'héritage de table se comporte avec les formulaires générés par symfony. Le Modèle de Données Etudié Le schéma de données YAML ci-dessous décrit un modèle destiné à la gestion de documents numériques. L'objectif de cette étude de cas consiste à sauvegarder des informations génériques dans le modèle File et les données spécifiques dans des tables filles telles que Vidéo et PDF. File: columns: filename: type: notnull: mime_type: type: notnull: description: type: notnull: size: type: notnull: default: Video: inheritance: type: extends: columns: format: type: notnull: duration: type: notnull: default: string(50) true string(50) true clob true integer(8) true 0 concrete File string(30) true integer(8) true 0 http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework encoding: type: PDF: tableName: inheritance: type: extends: columns: pages: type: notnull: default: paper_size: type: orientation: type: default: values: is_encrypted: type: default: notnull: string(50) pdf concrete File integer(8) true 0 string(30) enum portrait [portrait, landscape] boolean false true Les deux tables PDF et Video partagent la même table File, qui contient les informations générales des documents numériques téléchargés. Le modèle Vidéo encapsule les données relatives aux fichiers vidéos telles que le format (4/3, 16/9...) ou la durée (duration), tandis que le modèle PDF contient le nombre de pages ou l'orientation du document. La commande doctrine:build ci-dessous construit le modèle de données et les formulaires correspondants. $ php symfony doctrine:build --all La section suivante décrit comment tirer profit de l'héritage de table dans les classes de formulaires grâce à la nouvelle méthode setupInheritance(). A la Découverte de la Méthode setupInheritance() Comme attendu, Doctrine a généré six classes de formulaires dans les répertoires lib/form/doctrine/base et lib/form/doctrine. BaseFileForm BaseVideoForm BasePDFForm FileForm VideoForm PDFForm Il suffit d'ouvrir les trois classes préfixées par Base pour découvrir quelque chose de nouveau dans la méthode setup(). Une nouvelle méthode setupInheritance(), vide par défaut, a été ajoutée depuis symfony 1.3. Il est très important de remarquer que l'héritage du formulaire est conservé dans la mesure où les deux classes BaseVideoForm et BasePDFForm héritent des classes FileForm et BaseFileForm. Par conséquent, chacune d'entre elles dérivent la classe File et peut également partager les mêmes méthodes de base. Le listing suivant redéfinit la méthode setupInheritance() et configure la classe FileForm afin qu'elle puisse être réutilisée plus efficacement dans un autre sous-formulaire. // lib/form/doctrine/FileForm.class.php class FileForm extends BaseFileForm { protected function setupInheritance() { parent::setupInheritance(); $this->useFields(array('filename', 'description')); $this->widgetSchema['filename'] = new sfWidgetFormInputFile(); $this->validatorSchema['filename'] = new sfValidatorFile(array( 'path' => sfConfig::get('sf_upload_dir') )); http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework } } La méthode setupInheritance(), appelée par les deux sous-classes VideoForm et PDFForm, supprime tous les champs à l'exception de filename et description. Le widget du champ filename a été redéfini en un widget de téléchargement de fichier et son validateur correspondant a été remplacé par un objet sfValidatorFile. De cette manière, l'utilisateur sera capable de transmettre un fichier et de le sauvegarder sur le serveur. Définir le Type Mime et la Taille du Fichier Tous les formulaires sont désormais prêts et personnalisés. Il reste néanmoins une toute dernière chose à configurer avant de pouvoir les utiliser. Comme les widgets mime_type et size ont été retirés de l'objet FileForm, ils doivent être définis par programmation. Le meilleur endroit pour y parvenir est de passer par une nouvelle méthode generateFilenameFilename dans la classe File. // lib/model/doctrine/File.class.php class File extends BaseFile { /** * Generates a filename for the current file object. * * @param sfValidatedFile $file * @return string */ public function generateFilenameFilename(sfValidatedFile $file) { $this->setMimeType($file->getType()); $this->setSize($file->getSize()); return $file->generateFilename(); } } Cette nouvelle méthode a pour rôle de générer un nom de fichier personnalisé pour le fichier avant de le sauvegarder sur le système de fichiers. Bien que la méthode generateFilenameFilename() retourne un nom de fichier auto-généré par défaut, elle sert aussi à définir les propriétés mime_type et size à la volée en s'appuyant sur l'objet sfValidatedFile passé en premier argument. Depuis que symfony 1.3 supporte entièrement l'héritage de table Doctrine, les formulaires sont maintenant capables de sauver un objet ainsi que ses valeurs héritées. Le support natif de l'héritage permet ainsi de créer des formulaires puissants et fonctionnels avec seulement quelques lignes de code personnalisé. L'exemple ci-dessous aurait pu être largement et facilement amélioré grâce à l'héritage de classe. Par exemple, les deux classes VideoForm et PDFForm peuvent redéfinir le validateur du champ filename par un validateur plus spécifique tel que sfValidatorVideo ou bien sfValidatorPDF. Il suffit pour cela de créer les deux classes, de leur faire étendre sfValidatorFile et de spécialiser leur option mime_type respective. L'Héritage de Table au Niveau des Filtres Parce que les filtres sont aussi des formulaires, ils héritent eux aussi des propriétés et des http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework méthodes des formulaires de filtres parents. Par conséquent, les objets VideoFormFilter et PDFFormFilter étendent la classe FileFormFilter et peuvent ainsi être personnalisés en utilisant la méthode setupInheritance(). De la même manière, les deux classes VideoFormFilter et PDFFormFilter peuvent partager les mêmes méthodes personnalisées de la classe FileFormFilter. L'Héritage de Table avec l'Admin Generator Il est maintenant l'heure de découvrir comment tirer profit de l'héritage de table Doctrine dans l'Admin Generator grâce à l'une de ses nouvelles fonctionnalités : la définition d'une classe d'actions de base commune. Il faut savoir que l'Admin Generator est l'une des fonctionnalités les plus appréciées des développeurs depuis symfony 1.0. En novembre 2008, le framework symfony 1.2 s'est doté d'un tout nouveau système de génération de modules d'administration. Cet outil est livré par défaut avec de nombreuses fonctionnalités prêtes à l'emploi telles les opérations CRUD de base, le filtrage et la pagination de la liste de résultats, la suppression massive et bien plus encore... L'Admin Generator est un outil puissant qui facilite et accélère considérablement la génération et la personnalisation d'interfaces d'administration pour tout développeur. Introduction à l'Exemple Pratique L'objectif de la dernière partie de ce chapitre consiste à illustrer comment tirer parti de l'héritage de table Doctrine dans l'Admin Generator. Pour y parvenir, une petite interface d'administration sera bâtie. Cette dernière contiendra deux modules de gestion de tables qui contiennent toutes les deux des données qui peuvent être ordonnées et priorisées les unes par rapport aux autres. Comme la ligne de conduite de symfony est de ne pas réinventer la roue à chaque fois, le modèle de données Doctrine s'appuiera sur le plugin csDoctrineActAsSortablePlugin qui fournit toute l'API nécessaire à l'ordonnancement d'objets. Ce plugin est développé et maintenu par CentreSource, l'une des sociétés les plus actives dans l'écosystème de symfony. Le modèle de données est particulièrement simple pour cet exemple. Il consiste en trois classes de modèle, sfItem, sfTodoItem et sfShoppingItem, qui servent à gérer une liste de choses à faire et une liste de courses. Chaque enregistrement de ces deux listes est ordonnable afin de permettre aux objets d'être priorisés les uns par rapport aux autres. sfItem: actAs: columns: name: type: notnull: sfTodoItem: actAs: inheritance: type: extends: columns: priority: type: notnull: default: assigned_to: type: notnull: default: sfShoppingItem: actAs: inheritance: type: extends: columns: quantity: type: notnull: default: [Timestampable] string(50) true [Sortable] concrete sfItem string(20) true minor string(30) true me [Sortable] concrete sfItem integer(3) true 1 Le schéma ci-dessus décrit le modèle de données séparé en trois classes de modèle. Les deux classes filles, sfTodoItem et sfShoppingItem, utilisent toutes les deux les comportements http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework Sortable et Timestampable par héritage pour ce dernier. Le comportement Sortable est fourni par le plugin csDoctrineActAsSortableBehaviorPlugin qui ajoute une colonne supplémentaire position de type entier pour chaque table. Les deux classes étendent la classe de base sfItem, qui elle-même contient les colonnes id et name. L'étape suivante consiste à ajouter quelques données de test sur lesquelles l'interface d'administration pourra agir. Les jeux de données de test sont, comme toujours, situés dans le fichier data/fixtures.yml du projet symfony. sfTodoItem: sfTodoItem_1: name: priority: assigned_to: sfTodoItem_2: name: priority: assigned_to: sfTodoItem_3: name: priority: assigned_to: sfTodoItem_4: name: priority: assigned_to: sfShoppingItem: sfShoppingItem_1: name: quantity: sfShoppingItem_2: name: quantity: sfShoppingItem_3: name: quantity: sfShoppingItem_4: name: quantity: "Write a new symfony book" "medium" "Fabien Potencier" "Release Doctrine 2.0" "minor" "Jonathan Wage" "Release symfony 1.4" "major" "Kris Wallsmith" "Document Lime 2 Core API" "medium" "Bernard Schussek" "Apple MacBook Pro 15.4 inches" 3 "External Hard Drive 320 GB" 5 "USB Keyboards" 2 "Laser Printer" 1 Une fois le plugin csDoctrineActAsSortablePlugin correctement installé et le modèle de données prêt, le nouveau plugin requiert d'être activé dans la classe de configuration ProjectConfiguration du fichier config/ProjectConfiguration.class.php. class ProjectConfiguration extends sfProjectConfiguration { public function setup() { $this->enablePlugins(array( 'sfDoctrinePlugin', 'csDoctrineActAsSortablePlugin' )); } } Ensuite, la base de données, le modèle, les formulaires et les filtres doivent être générés et les données initiales chargées dans la base afin d'alimenter les nouvelles tables créées. Cette tâche est accomplie en une fois grâce à la commande doctrine:build de symfony. $ php symfony doctrine:build --all --no-confirmation Le cache de symfony doit enfin être nettoyé pour finaliser le processus, et les ressources web des plugins doivent être publiées sous la racine web. $ php symfony cache:clear $ php symfony plugin:publish-assets La section suivante explique enfin comment construire pas à pas les modules d'administration avec les outils de l'Admin Generator, et comment profiter des classes d'actions de base personnalisées. Construire le Backend http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework Cette partie du chapitre décrit les étapes nécessaires pour configurer une nouvelle application d'administration. Celle-ci contiendra les deux modules générés qui gèrent les listes de courses et de tâches. Par conséquent, la première chose à réaliser est de générer une application backend pour héberger ces futurs modules. $ php symfony generate:app backend Avant symfony 1.3, et bien que l'Admin Generator soit un outil très abouti, les développeurs étaient néanmoins contraints de dupliquer du code commun entre les différents modules générés. Or, aujourd'hui, la tâche doctrine:generate-admin introduit une nouvelle option -actions-base-class qui permet au développeur de définir une classe d'actions de base pour chaque module généré. Comme les deux modules à générer sont sensiblement les mêmes, ils auront certainement besoin de partager quelques actions communes. Le code de ces dernières peut ainsi être localisé dans une classe d'actions plus générique située dans le répertoire lib/actions du projet. Le code de cette classe maîtresse est présentée ci-dessous. // lib/actions/sfSortableModuleActions.class.php class sfSortableModuleActions extends sfActions { } Après que la nouvelle classe sfSortableModuleActions ait été créée et le cache de symfony nettoyé, les deux modules de l'application peuvent alors être générés dans l'application backend. $ php symfony doctrine:generate-admin --module=shopping --actions-baseclass=sfSortableModuleActions backend sfShoppingItem $ php symfony doctrine:generate-admin --module=todo --actions-baseclass=sfSortableModuleActions backend sfTodoItem L'Admin Generator génère des modules dans deux répertoires séparés. Le premier répertoire conteneur est bien sûr apps/backend/modules bien qu'en réalité la majorité des modules générés se trouve dans le répertoire cache/backend/dev/modules. Tous les fichiers localisés dans ce répertoire sont régénérés à chaque fois que le cache est nettoyé ou bien lorsque la configuration d'un module change. Parcourir les fichiers mis en cache est une excellente manière de mieux comprendre comment symfony et l'Admin Generator fonctionnent ensemble sous le capot. Par conséquent, les nouvelles classes dérivées de sfSortableModuleActions peuvent être retrouvées respectivement dans les fichiers cache/backend/dev/modules/autoShopping/actions/actions.class.php et cache/backend/dev/modules/autoTodo/actions/actions.class.php. Par défaut, symfony aurait généré ces classes en les faisant hériter de la classe sfActions. http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework Les deux modules d'administration sont à présent prêts à être utilisés et personnalisés. La personnalisation des modules d'administration ne constitue pas l'objet de ce chapitre car cette tâche est particulièrement bien documentée, à commencer par le livre de référence : symfony Reference Book. Modifier la Position d'un Enregistrement La section précédente a décrit comment construire deux modules d'administration entièrement fonctionnels et qui héritent de la même classe d'actions. L'objectif suivant est de créer une action partagée qui offre au développeur la possibilité d'ordonner les objets d'une liste les uns par rapport aux autres. Satisfaire ce besoin fonctionnel est une tâche aisée dans la mesure où le plugin installé fournit une API complète pour manipuler les changements de position des objets. La première étape à réaliser pour y parvenir consiste tout d'abord à créer deux nouvelles routes capables de déplacer un enregistrement vers le haut ou vers le bas dans la liste. Comme le générateur d'administration s'appuie sur une route sfDoctrineRouteCollection, les nouvelles routes peuvent être déclarées et attachées à la collection grâce au fichier de configuration config/generator.yml de chaque module: # apps/backend/modules/shopping/config/generator.yml generator: class: sfDoctrineGenerator param: model_class: sfShoppingItem theme: admin non_verbose_templates: true with_show: false singular: ~ plural: ~ route_prefix: sf_shopping_item with_doctrine_route: true actions_base_class: sfSortableModuleActions config: actions: ~ fields: ~ list: max_per_page: 100 sort: [position, asc] display: [position, name, quantity] object_actions: moveUp: { label: "move up", action: "moveUp" } moveDown: { label: "move down", action: "moveDown" } _edit: ~ _delete: ~ filter: ~ form: ~ edit: ~ new: ~ Les modifications apportées dans le fichier précédent doivent être répétées pour le fichier de configuration du module todo comme le montre le code ci-dessous. # apps/backend/modules/todo/config/generator.yml generator: http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework class: sfDoctrineGenerator param: model_class: sfTodoItem theme: admin non_verbose_templates: true with_show: false singular: ~ plural: ~ route_prefix: sf_todo_item with_doctrine_route: true actions_base_class: sfSortableModuleActions config: actions: ~ fields: ~ list: max_per_page: sort: display: object_actions: moveUp: moveDown: _edit: ~ _delete: ~ filter: ~ form: ~ edit: ~ new: ~ 100 [position, asc] [position, name, priority, assigned_to] { label: "move up", action: "moveUp" } { label: "move down", action: "moveDown" } Ces deux fichiers YAML décrivent la configuration pour les deux modules shopping et todo. Chacun d'entre eux a été personnalisé afin de convenir aux besoins de l'utilisateur final. Tout d'abord, la vue liste est à présent triée par ordre ascendant suivant la colonne position. Ensuite, le nombre maximum d'enregistrements par page a été augmenté jusqu'à la valeur 100 afin d'empêcher une pagination trop hâtive. Enfin, le nombre de colonnes affichées a été réduit à l'ensemble suivant : position, name, priority, assigned_to et quantity. De plus, chaque module dispose désormais de deux nouvelles actions : moveUp et moveDown. Le rendu final de ces deux modules devrait ressembler aux captures d'écran ci-après. http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework Ces deux nouvelles actions ont été déclarées mais ne réalisent encore rien pour le moment. Chacune d'entre elles doit être explicitement créée dans la classe d'actions partagées, sfSortableModuleActions, comme le montre le listing ci-dessous. Le plugin csDoctrineActAsSortablePlugin fournit deux méthodes supplémentaires pour chaque objet de modèle : promote() et demote(). Ces deux méthodes sont ici respectivement utilisées dans les actions moveUp et moveDown. // lib/actions/sfSortableModuleActions.class.php class sfSortableModuleActions extends sfActions { /** * Moves an item up in the list. * * @param sfWebRequest $request */ public function executeMoveUp(sfWebRequest $request) { $this->item = $this->getRoute()->getObject(); $this->item->promote(); $this->redirect($this->getModuleName()); } /** * Moves an item down in the list. * * @param sfWebRequest $request */ public function executeMoveDown(sfWebRequest $request) { $this->item = $this->getRoute()->getObject(); $this->item->demote(); $this->redirect($this->getModuleName()); } } Grâce à ces deux actions partagées, les deux listes de courses et de tâches sont désormais ordonnables. Par ailleurs, elles sont beaucoup plus faciles à maintenir et à tester à l'aide de tests fonctionnels. N'hésitez pas à améliorer l'interface et l'expérience utilisateur de chaque module en redéfinissant les templates d'actions d'objet afin de supprimer le premier lien moveUp et le dernier lien moveDown de chaque liste. Bonus: Améliorer l'Expérience Utilisateur Avant de clore ce chapitre, il convient de parfaire les deux listes en améliorant l'expérience utilisateur. Tout le monde s'accorde à dire que déplacer un enregistrement vers le haut (ou vers le bas) en cliquant sur un lien n'est pas si intuitif qu'il n'y parait pour l'utilisateur final. Une bien meilleure approche consiste définitivement à inclure des comportements JavaScript et Ajax pour rendre les lignes du tableau déplaçables. Une fois de plus, il n'est pas question de réinventer la roue, c'est pourquoi ces comportements Ajax seront traités à l'aide du plugin jQuery Table Drag and Drop. Un appel Ajax sera exécuté à chaque fois que l'utilisateur http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework déplacera une ligne du tableau HTML à une autre position dans celui-ci. La première étape de cette nouvelle série d'améliorations consiste à récupérer et installer le framework jQuery dans le répertoire web/js. Puis il s'agit de répéter cette même opération pour le plugin Table Drag and Drop, dont les sources sont hébergées dans un dépôt Subversion Google Code. Il ne reste plus qu'à inclure ces deux scripts JavaScript dans le layout de l'application ou bien dans le fichier de configuration view.yml. Pour fonctionner, la vue liste de chaque module doit inclure un petit morceau de code JavaScript et les deux tableaux HTML doivent également posséder un attribut id. Comme tous les templates et les partiels de l'Admin Generator sont surchargeables, le fichier _list.php (situé dans le cache par défaut) devrait alors être copié dans chaque module. Cependant, copier le fichier _list.php dans le répertoire templates/ de chaque module n'est pas tout à fait DRY. Il suffit en effet de copier le fichier cache/backend/dev/modules/autoShopping/templates/_list.php dans le répertoire apps/backend/templates, puis le renommer en _table.php. Ensuite, le contenu actuel de ce nouveau fichier doit être remplacé par le code suivant. <div class="sf_admin_list"> <?php if (!$pager->getNbResults()): ?> <p><?php echo __('No result', array(), 'sf_admin') ?></p> <?php else: ?> <table cellspacing="0" id="sf_item_table"> <thead> <tr> <th id="sf_admin_list_batch_actions"><input id="sf_admin_list_batch_checkbox" type="checkbox" onclick="checkAll();" /></th> <?php include_partial( $sf_request->getParameter('module').'/list_th_tabular', array('sort' => $sort) ) ?> <th id="sf_admin_list_th_actions"> <?php echo __('Actions', array(), 'sf_admin') ?> </th> </tr> </thead> <tfoot> <tr> <th colspan="<?php echo $colspan ?>"> <?php if ($pager->haveToPaginate()): ?> <?php include_partial( $sf_request->getParameter('module').'/pagination', array('pager' => $pager) ) ?> <?php endif; ?> <?php echo format_number_choice( '[0] no result|[1] 1 result|(1,+Inf] %1% results', array('%1%' => $pager->getNbResults()), $pager->getNbResults(), 'sf_admin' ) ?> <?php if ($pager->haveToPaginate()): ?> <?php echo __('(page %%page%%/%%nb_pages%%)', array( '%%page%%' => $pager->getPage(), '%%nb_pages%%' => $pager->getLastPage()), 'sf_admin' ) ?> <?php endif; ?> </th> </tr> </tfoot> <tbody> <?php foreach ($pager->getResults() as $i => $item): ?> <?php $odd = fmod(++$i, 2) ? 'odd' : 'even' ?> <tr class="sf_admin_row <?php echo $odd ?>"> <?php include_partial( $sf_request->getParameter('module').'/list_td_batch_actions', array( 'sf_'. $sf_request->getParameter('module') .'_item' => $item, 'helper' => $helper )) ?> <?php include_partial( http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework $sf_request->getParameter('module').'/list_td_tabular', array( 'sf_'. $sf_request->getParameter('module') .'_item' => $item )) ?> <?php include_partial( $sf_request->getParameter('module').'/list_td_actions', array( 'sf_'. $sf_request->getParameter('module') .'_item' => $item, 'helper' => $helper )) ?> </tr> <?php endforeach; ?> </tbody> </table> <?php endif; ?> </div> <script type="text/javascript"> /* <![CDATA[ */ function checkAll() { var boxes = document.getElementsByTagName('input'); for (var index = 0; index < boxes.length; index++) { box = boxes[index]; if ( box.type == 'checkbox' && box.className == 'sf_admin_batch_checkbox' ) box.checked = document.getElementById('sf_admin_list_batch_checkbox').checked } return true; } /* ]]> */ </script> Enfin, il ne reste plus qu'à créer un nouveau fichier _list.php à l'intérieur de chaque répertoire templates/ des deux modules, et d'ajouter les codes suivants dans chacun d'eux. // apps/backend/modules/shopping/templates/_list.php <?php include_partial('global/table', array( 'pager' => $pager, 'helper' => $helper, 'sort' => $sort, 'colspan' => 5 )) ?> // apps/backend/modules/shopping/templates/_list.php <?php include_partial('global/table', array( 'pager' => $pager, 'helper' => $helper, 'sort' => $sort, 'colspan' => 8 )) ?> Pour changer la position d'une ligne, les deux modules ont besoin d'implémenter une nouvelle action qui se charge de traiter la requête ajax à venir. Comme il l'a été montré précédemment dans ce chapitre, la nouvelle action executeMove() trouve naturellement sa place dans la classe d'actions communes sfSortableModuleActions. // lib/actions/sfSortableModuleActions.class.php class sfSortableModuleActions extends sfActions { /** * Performs the Ajax request, moves an item to a new position. * * @param sfWebRequest $request */ public function executeMove(sfWebRequest $request) { $this->forward404Unless($request->isXmlHttpRequest()); $this->forward404Unless($item = Doctrine_Core::getTable($this->configuration>getModel())->find($request->getParameter('id'))); http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework $item->moveToPosition((int) $request->getParameter('rank', 1)); return sfView::NONE; } } L'action executeMove() fait appel à une méthode getModel() sur l'objet de configuration de l'Admin Generator. Cette nouvelle méthode doit donc être implémentée dans les deux classes de configuration todoGeneratorConfiguration et shoppingGeneratorConfiguration comme expliqué ci-dessous. // apps/backend/modules/shopping/lib/shoppingGeneratorConfiguration.class.php class shoppingGeneratorConfiguration extends BaseShoppingGeneratorConfiguration { public function getModel() { return 'sfShoppingItem'; } } // apps/backend/modules/todo/lib/todoGeneratorConfiguration.class.php class todoGeneratorConfiguration extends BaseTodoGeneratorConfiguration { public function getModel() { return 'sfTodoItem'; } } Il reste encore une toute dernière opération à réaliser. En effet, pour l'instant, les lignes des deux tableaux ne sont pas déplaçables et aucune requête ajax n'est exécutée lorsqu'une ligne du tableau est relâchée. Pour y parvenir, les deux modules ont besoin chacun d'une route spécifique pour accéder à leur action move respective. Par conséquent, le fichier apps/backend/config/routing.yml doit accueillir les deux nouvelles routes ci-dessous. <?php foreach (array('shopping', 'todo') as $module) : ?> <?php echo $module ?>_move: class: sfRequestRoute url: /<?php echo $module ?>/move param: module: "<?php echo $module ?>" action: move requirements: sf_method: [get] <?php endforeach ?> Afin d'éviter la duplication de code, les deux routes sont générées dynamiquement à l'intérieur d'une boucle foreach, et leur identifiant repose sur le nom du module afin de pouvoir les retrouver facilement dans la vue. Enfin, il ne reste plus que le fichier apps/backend/templates/_table.php qui doit implémenter le code JavaScript nécessaire à l'initialisation du comportement de glisser / déposer, et les requêtes Ajax correspondantes. <script type="text/javascript" charset="utf-8"> $().ready(function() { $("#sf_item_table").tableDnD({ onDrop: function(table, row) { var rows = table.tBodies[0].rows; // Get the moved item's id var movedId = $(row).find('td input:checkbox').val(); // Calculate the new row's position var pos = 1; for (var i = 0; i<rows.length; i++) { var cells = rows[i].childNodes; // Perform the ajax request for the new position if (movedId == $(cells[1]).find('input:checkbox').val()) { $.ajax({ http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Tirer Profit de l'Héritage de Table avec Doctrine | symfony | Web PHP Framework url:"<?php echo url_for('@'. $sf_request>getParameter('module').'_move') ?>?id="+ movedId +"&rank="+ pos, type:"GET" }); break; } pos++; } }, }); }); </script> Le tableau HTML est à présent entièrement fonctionnel. Les lignes sont glissables et déposables, et la nouvelle position d'une ligne déplacée est automatiquement sauvée à l'aide d'un appel Ajax. Avec seulement quelques lignes de code, l'usabilité de l'interface d'administration a été grandement améliorée afin d'offrir une meilleure expérience utilisateur. L'Admin Generator est suffisamment flexible pour être étendu et personnalisé, et il fonctionne de plus parfaitement avec l'héritage de table Doctrine. N'hésitez pas à améliorer ces deux modules en retirant les actions moveUp et moveDown obsolètes, ou bien en ajoutant d'autres personnalisations qui conviennent à vos besoins. Conclusion Ce chapitre a décrit combien l'héritage de table de Doctrine est une puissante fonctionnalité qui aide le développeur à coder plus vite, mais aussi à améliorer significativement l'organisation de son code. Cet outil de Doctrine est entièrement intégré à plusieurs niveaux dans symfony. Ainsi, les développeurs sont désormais fortement encouragés à l'utiliser et en tirer parti dans le but d'augmenter leur efficacité, d'améliorer l'organisation de leur code et bien sûr de parfaire la productivité de leurs projets. « Techniques Avancées avec Doctrine Plonger dans les Entrailles de Symfony » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/09-Doctrine-Form-Inheritance[31/12/2010 02:34:10] The More with symfony book | Plonger dans les Entrailles de Symfony | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Plonger dans les Entrailles de Symfony You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Geoffrey Bachelet Vous-êtes vous déjà posé la question de savoir ce qui arrive à une requête HTTP lorsqu'elle atteint une application symfony ? Si oui, vous êtes au bon endroit. Ce chapitre expliquera en profondeur comment symfony traite chaque requête pour créer et retourner une réponse. Bien sur, il ne décrira pas seulement le processus car cela manquerait d'intérêt. Par conséquent, ce chapitre s'intéressa à étudier ce qu'il est possible de réaliser et à quels endroits le développeur peut interagir au cours de ce processus. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. L'Amorçage : le Bootstrap Tout commence dans le contrôleur de l'application. En considérant que le projet est configuré de manière traditionnelle avec un contrôleur frontend et un environnement de développement dev, il en résulte donc qu'un contrôleur frontal pour cette configuration est présent dans le fichier web/frontend_dev.php. Que se passe-t-il exactement dans ce fichier ? En quelques lignes de code, symfony récupère la configuration de l'application puis crée une instance de la classe sfContext, qui se charge de l'expédition de la requête. La configuration de l'application est nécessaire lors de la création de l'objet sfContext, qui demeure également le moteur dont dépend l'application. Symfony donne déjà un peu de contrôle au développeur sur ce qu'il se passe en lui permettant de passer un répertoire racine personnalisé en quatrième argument de ProjectConfiguration::getApplicationConfiguration(), ainsi qu'une classe de contexte personnalisée dans le troisième (et dernier) argument de sfContext::createInstance(). Cette classe doit bien évidemment étendre la classe originale sfContext. La récupération de la configuration de l'application est une étape très importante. Tout d'abord, l'objet sfProjectConfiguration s'occupe de deviner la classe de configuration de l'application, qui se trouve plus généralement dans la classe ${application}Configuration du fichier apps/${application}/config/${application}Configuration.class.php. La classe sfApplicationConfiguration étend en réalité la classe ProjectConfiguration, ce qui signifie que toute méthode définie dans ProjectConfiguration est ainsi partagée entre toutes les applications. Cela signifie aussi que la classe sfApplicationConfiguration partage son constructeur avec les classes ProjectConfiguration et sfProjectConfiguration. C'est un avantage car la plupart du projet est configurée dans le constructeur de sfProjectConfiguration. Dans un premier temps, plusieurs valeurs utiles seront calculées et stockées. Ces dernières contiennent par exemple les variables de configuration du répertoire parent de l'application ainsi que le répertoire dans lequel se trouvent les librairies de symfony. L'objet sfProjectConfiguration crée aussi un nouvel objet d'expédition d'évènement, "l'event dispatcher", de type sfEventDispatcher ; à moins qu'un autre événement ait été fourni comme cinquième argument de la méthode statique ProjectConfiguration::getApplicationConfiguration() du contrôleur frontal. Juste après cela, le développeur a la possibilité d'interagir avec le processus de configuration en redéfinissant la méthode setup() de l'objet ProjectConfiguration. C'est en effet le meilleur endroit pour activer ou bien désactiver des plugins en utilisant au choix les méthodes d'instance sfProjectConfiguration::setPlugins(), sfProjectConfiguration::enablePlugins(), sfProjectConfiguration::disablePlugins() or sfProjectConfiguration::enableAllPluginsExcept(). Ensuite, les plugins sont chargés par l'intermédiaire de la méthode sfProjectConfiguration::loadPlugins(), et le développeur peut alors prendre le contrôle sur http://www.symfony-project.org/more-with-symfony/1_4/fr/10-Symfony-Internals[31/12/2010 02:34:18] Chapter Content L'Amorçage : le Bootstrap Procédure d'Amorçage et Résumé de la Configuration L'Objet sfContext et les Factories Utiliser l'Evénement request.filter_parameter Exemple d'Utilisation de l'Evénement routing.load_configuration Tirer Profit de l'Evénement template.filter_parameters Résumé de sfContext Quelques Mots à propos des Gestionnaires de Configuration L'Expédition et l'Exécution de la Requête Résumé du Processus d'Expédition La Chaîne de Filtres The More with symfony book | Plonger dans les Entrailles de Symfony | symfony | Web PHP Framework ce processus via la méthode sfProjectConfiguration::setupPlugins() qui peut aussi être redéfinie. Le Filtre de Sécurité L'initialisation des plugins est assez simple. Pour chaque plugin, symfony vérifie l'existence d'une classe ${plugin}Configuration, par exemple sfGuardPluginConfiguration, qu'il instancie si elle existe. Sinon, la classe sfPluginConfigurationGeneric est utilisée. Il est possible d'intervenir dans la configuration d'un plugin à travers deux méthodes. Le Filtre de Rendu ${plugin}Configuration::configure(), avant que l'autochargement de classes soit réalisé, Le Filtre du Cache Le Filtre d'Exécution Résumé de l'Exécution de la Chaîne de Filtrage Résumé général Conclusion ${plugin}Configuration::initialize(), après l'autochargement de classes. Puis, l'objet sfApplicationConfiguration exécute sa méthode configure(), qui peut alors être utilisée pour personnaliser chaque configuration d'application avant que l'essentiel du processus d'initialisation de configuration interne ne commence réellement dans sfApplicationConfiguration::initConfiguration(). Cette partie du processus de configuration de symony s'occupe de plein de choses et il y a plusieurs points d'entrées pour lesquels le développeur a la capacité d'agir au cours du processus. Par exemple, il peut interagir avec la configuration de l'autochargeur de classes en le connectant à l'évènement autoload.filter_config. Puis, plusieurs fichiers de configuration importants sont chargés, dont les fichiers settings.yml and app.yml. Enfin, un dernier morceau de configuration est disponible à travers chaque fichier config/config.php ou alors à partir de la méthode initialize() de la classe de configuration. Si le paramètre sf_check_lock est activé, symfony cherchera un fichier de verrouillage, qui aura été créé par l'exécution de la commande project:disable par exemple. Si ce fichier existe, les fichiers suivants sont recherchés, et le premier qui est disponible est alors inclus. A ce moment là, le script se termine immédiatement. 1. apps/${application}/config/unavailable.php, 2. config/unavailable.php, 3. web/errors/unavailable.php, 4. lib/vendor/symfony/lib/exception/data/unavailable.php, Enfin, le développeur dispose encore d'une dernière chance de personnaliser l'initialisation de l'application à travers la méthode sfApplicationConfiguration::initialize(). Procédure d'Amorçage et Résumé de la Configuration Récupération de la configuration de l'application, ProjectConfiguration::setup() (définition des plugins ici), Chargement des plugins, ${plugin}Configuration::configure(), ${plugin}Configuration::initialize(), ProjectConfiguration::setupPlugins() (installation des plugins ici), ${application}Configuration::configure(), autoload.filter_config est notifié, Chargement des fichiers settings.yml et app.yml, ${application}Configuration::initialize(), Création d'une instance de sfContext. L'Objet sfContext et les Factories Avant de plonger dans le processus d'expédition, il convient de parler d'un point vital du flux de traitement de symfony : les factories. Dans symfony, les factories sont un ensemble de composants ou de classes sur lesquels se base l'application. logger, i18n sont des exemples de ces factories, et chacune d'elles est configurée dans le fichier factories.yml. Ce dernier est ensuite compilé par un gestionnaire de configuration (il sera évoqué plus tard) et converti en code PHP brut qui instancie les objets factory. Le code compilé est disponible dans le cache du projet dans le fichier cache/frontend/dev/config/config_factories.yml.php. Le chargement des factories intervient après l'initialisation de sfContext. Pour plus d'informations, veuillez consulter sfContext::initialize() et sfContext::loadFactories() http://www.symfony-project.org/more-with-symfony/1_4/fr/10-Symfony-Internals[31/12/2010 02:34:18] Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Search powered by google The More with symfony book | Plonger dans les Entrailles de Symfony | symfony | Web PHP Framework Au point où nous en sommes, il est déjà possible de personnaliser une grande partie du comportement de symfony en éditant le fichier de configuration factories.yml. Les classes factory de symfony peuvent d'ailleurs même être remplacées par des classes personnalisées. Pour en savoir plus au sujet des factories, Le Livre de Référence de symfony et le fichier factories.yml sont des ressources inestimables. En regardant de plus près le fichier config_factories.yml.php généré, on s'aperçoit que les factories sont instanciées dans un ordre précis. Ce dernier est très important car certaines factories sont dépendantes des autres. C'est le cas, par exemple, du composant routing qui a bien évidemment besoin du composant request pour retrouver les informations dont il a besoin. Qu'en est-il par exemple de la factory request ? Par défaut, la classe sfWebRequest représente l'objet de la requête de l'utilisateur (request). A l'instanciation, sfWebRequest::initialize() est appelée, et collecte diverses informations pertinentes telles que les paramètres GET / POST et la méthode HTTP utilisée. Des traitements complémentaires sur cet objet peuvent être réalisés à l'aide de l'évènement request.filter_parameters. Utiliser l'Evénement request.filter_parameter En supposant par exemple un site qui propose une API publique aux utilisateurs, celle-ci sera rendue disponible à travers le protocole HTTP, et chaque utilisateur désirant en faire usage devra fournir une clé valide dans l'en-tête de la requête (par exemple X_API_KEY) afin d'être autorisé par l'application. Ce mécanisme peut être mis en place très facilement en utilisant l'événement request.filter_parameter. class apiConfiguration extends sfApplicationConfiguration { public function configure() { // ... $this->dispatcher->connect('request.filter_parameters', array( $this, 'requestFilterParameters' )); } public function requestFilterParameters(sfEvent $event, $parameters) { $request = $event->getSubject(); $api_key = $request->getHttpHeader('X_API_KEY'); if (null === $api_key || false === $api_user = Doctrine_Core::getTable('ApiUser')>findOneByToken($api_key)) { throw new RuntimeException(sprintf('Invalid api key "%s"', $api_key)); } $request->setParameter('api_user', $api_user); return $parameters; } } La valeur de la clé utilisateur de l'API publique sera donc accessible depuis l'objet de requête de symfony. public function executeFoobar(sfWebRequest $request) { $api_user = $request->getParameter('api_user'); } Cette technique peut servir entre autres à valider des appels sur des services web. L'événement request.filter_parameters intègre de nombreuses informations sur la requête. L'API de la méthode sfWebRequest::getRequestContext() est disponible en ligne pour plus d'informations. http://www.symfony-project.org/more-with-symfony/1_4/fr/10-Symfony-Internals[31/12/2010 02:34:18] The More with symfony book | Plonger dans les Entrailles de Symfony | symfony | Web PHP Framework La factory suivante importante concerne le routage. L'initialisation du routage est relativement simple et consiste principalement en la récupération et le paramétrage de certaines options. Des interactions complémentaires avec le processus sont disponibles au travers de l'événement routing.load_configuration. L'événement routing.load_configuration donne accès à l'instance de l'objet de routing courant Par défaut, il s'agit de l'objet sfPatternRouting). Les routes enregistrées sont alors manipulables de plusieurs manières. Exemple d'Utilisation de l'Evénement routing.load_configuration Par exemple, il est possible d'ajouter une route très facilement de manière programmatique. public function setup() { // ... $this->dispatcher->connect('routing.load_configuration', array( $this, 'listenToRoutingLoadConfiguration' )); } public function listenToRoutingLoadConfiguration(sfEvent $event) { $routing = $event->getSubject(); if (!$routing->hasRouteName('my_route')) { $routing->prependRoute('my_route', new sfRoute( '/my_route', array('module' => 'default', 'action' => 'foo') )); } } L'analyse de l'URL intervient après l'initialisation, via la méthode sfPatternRouting::parse(). Plusieurs méthodes sont impliquées dans ce processus, mais il suffit de dire que lorsque l'exécution de la méthode parse() arrive à sa fin, la route correspondante a été trouvée, instanciée et liée à des paramètres pertinents. Pour plus d'informations au sujet du routing, le lecteur est invité à se reporter au chapitre Techniques Avancées de Routage de cet ouvrage. Lorsque toutes les factories ont été chargées et correctement initialisées, l'événement context.load_factories est alors déclenché. Cet événement est important car c'est celui, au niveau du framework, qui intervient le plus tôt et pour lequel le développeur peut avoir accès à tous les objets factory de base de symfony (requête, réponse, utilisateur, identification, base de données, etc.). Il est aussi temps de se connecter à un autre événement très utile : l'événement template.filter_parameters qui se produit chaque fois qu'un fichier est rendu par l'objet sfPHPView. Il permet notamment au développeur de contrôler les paramètres envoyés au template. L'objet sfContext utilise cet événement pour ajouter des paramètres utiles à chaque template tels que les variables $sf_context, $sf_request, $sf_params, $sf_response et $sf_user. Se connecter à l'événement template.filter_parameters peut ainsi permettre de passer des paramètres globaux additionnels aux templates. Tirer Profit de l'Evénement template.filter_parameters Il arrive parfois que tous les templates d'une même application aient besoin d'avoir accès à un objet particulier supplémentaire : un objet helper par exemple. Grâce à l'évènement template.filter_parameters, le développeur est capable de transmettre à tous les templates de l'application des paramètres supplémentaires de manière élégante en surchargeant la classe ProjectConfiguration du projet. public function setup() { // ... http://www.symfony-project.org/more-with-symfony/1_4/fr/10-Symfony-Internals[31/12/2010 02:34:18] The More with symfony book | Plonger dans les Entrailles de Symfony | symfony | Web PHP Framework $this->dispatcher->connect('template.filter_parameters', array( $this, 'templateFilterParameters' )); } public function templateFilterParameters(sfEvent $event, $parameters) { $parameters['my_helper_object'] = new MyHelperObject(); return $parameters; } A présent, chaque template a accès à une instance de la classe MyHelperObject à travers la variable $my_helper_object. Résumé de sfContext 1. Initialisation de sfContext 2. Chargement des factories 3. Notifications des événements suivants : 1. request.filter_parameters 2. routing.load_configuration 3. context.load_factories 4. Ajout des paramètres globaux aux templates Quelques Mots à propos des Gestionnaires de Configuration Les gestionnaires de configuration, appelés aussi config handlers dans symfony, sont au coeur du système de configuration du framework. Un gestionnaire de configuration a pour objectif de comprendre ce qui se cache derrière un fichier de configuration. Chaque gestionnaire de configuration est une classe qui est utilisée pour convertir un ensemble de fichiers de configuration YAML en un bloc de code PHP qui peut être exécuté au besoin. Chaque fichier de configuration est affecté à un gestionnaire de configuration spécifique dans le fichier config_handlers.yml. Pour être plus précis, la responsabilité d'un gestionnaire de configuration n'est pas d'analyser les fichiers YAML car cette tâche est déjà remplie par la classe sfYaml. Chaque gestionnaire de configuration crée un ensemble de directives PHP basées sur les informations du YAML et sauvegarde ces dernières dans un fichier PHP qui pourra être chargé plus tard. La version compilée de chaque fichier de configuration YAML peut être récupérée dans le répertoire de cache. Le gestionnaire de configuration utilisé le plus fréquemment est très certainement sfDefineEnvironmentConfigHandler, qui permet de configurer des données en fonction de l'environnement d'exécution. Ce gestionnaire de configuration s'occupe de récupérer uniquement les paramètres de l'environnement courant. Toujours pas convaincu ? Les quelques lignes suivantes expliquent ce que réalise le gestionnaire de configuration sfFactoryConfigHandler. Ce gestionnaire de configuration est utilisé pour compiler le fichier factories.yml, qui est l'un des fichiers de configuration les plus importants dans symfony. Il est particulier dans la mesure où il convertit un fichier de configuration YAML en un code PHP qui se chargera à son tour d'instancier les factories. C'est ce dont il a été question un peu plus haut dans ce chapitre. Cette classe n'est donc pas n'importe quel gestionnaire de configuration, n'est-ce pas ? L'Expédition et l'Exécution de la Requête Assez parlé des factories, il s'agit à présent d'aborder le processus d'expédition de la requête, le dispatching. Une fois l'initialisation de l'objet sfContext terminée, la dernière étape consiste à appeler la méthode dispatch() du contrôleur sfFrontWebController::dispatch(). Le processus d'expédition de symfony est très simple. En réalité, sfFrontWebController::dispatch(), prend les nom du module et de l'action depuis les paramètres de la requête, puis les transmet à l'application via la méthode sfController::forward(). A ce stade, si le routage n'a pas pu récupérer un nom de module ou un nom d'action depuis l'url courante, une exception sfError404Exception est levée, ce qui conduit http://www.symfony-project.org/more-with-symfony/1_4/fr/10-Symfony-Internals[31/12/2010 02:34:18] The More with symfony book | Plonger dans les Entrailles de Symfony | symfony | Web PHP Framework automatiquement au transfert de la requête vers le module qui s'occupe de traiter les erreurs 404 (voir sf_error_404_module et sf_error_404_action). Il est important de noter qu'il suffit de lever ce type d'exception depuis n'importe où dans l'application afin de reproduire ce résultat. La méthode forward est responsable d'une multitude de vérifications de pré-exécution, mais également de la préparation et de la configuration des données de l'action qui sera exécutée. Tout d'abord, le contrôleur vérifie la présence d'un fichier generator.yml pour le module courant. Cette vérification est effectuée en premier (après un bref nettoyage des noms du module et de l'action) car le fichier de configuration generator.yml, s'il existe, s'occupe de la génération de la classe d'actions de base pour le module (à travers son gestionnaire de configuration, sfGeneratorConfigHandler). C'est d'autant plus nécessaire pour l'étape suivante du fait de la vérification de l'existence du module et de l'action. C'est le contrôleur qui s'en occupe, à travers la méthode sfController::actionExists(), qui appelle en retour la méthode sfController::controllerExists() Ici encore, si la méthode actionExists() renvoie faux, une exception sfError404Exception est levée. L'objet sfGeneratorConfigHandler est un gestionnaire de configuration spécial qui s'occupe d'instancier la bonne classe de génération pour le module avant de l'exécuter. Pour plus d'informations sur les gestionnaires de configuration, référez-vous à la section Quelques mots à propos des gestionnaires de configuration de ce chapitre. Par ailleurs, pour en savoir plus sur le fichier generator.yml, veuillez consulter le chapitre 6 du livre de référence de symfony. Il n'est pas possible de faire plus à ce stade, si ce n'est de surcharger la méthode sfApplicationConfiguration::getControllerDirs() dans la classe de configuration de l'application. Cette méthode retourne un tableau de répertoires dans lesquels se trouvent les fichiers du contrôleur, avec un paramètre additionnel pour spécifier à symfony s'il doit vérifier ou non l'activation des contrôleurs via l'option de configuration sf_enabled_modules de settings.yml. Par exemple, getControllerDirs() pourrait ressembler à quelque chose de similaire au code ci-dessous. /** * Controllers in /tmp/myControllers won't need to be enabled * to be detected */ public function getControllerDirs($moduleName) { return array_merge(parent::getControllerDirs($moduleName), array( '/tmp/myControllers/'.$moduleName => false )); } Si l'action n'existe pas, une exception sfError404Exception est levée. La prochaine étape consiste à récupérer une instance du contrôleur contenant l'action. Cette étape est gérée par la méthode sfController::getAction() qui, comme la méthode actionExists(), est une façade pour la méthode sfController::getController(). Finalement, l'instance du contrôleur est ajoutée à la pile d'appel des actions : action stack. La pile d'appel des actions est une pile de type FIFO (First In First Out) qui contient toutes les actions exécutées pendant la requête courante. Chaque élément de la pile est encapsulé dans un objet sfActionStackEntry. La pile est quant à elle disponible à partie de la méthode sfContext::getInstance()->getActionStack() ou bien avec le code $this>getController()->getActionStack() depuis une action. Après quelques chargements de configuration supplémentaires, l'application sera prête à exécuter l'action demandée. La configuration spécifique au module doit toujours être chargée, et peut être trouvée à deux endroits différents. Tout d'abord, symfony cherchera après le fichier module.yml, normalement présent dans apps/frontend/modules/yourModule/config/module.yml. http://www.symfony-project.org/more-with-symfony/1_4/fr/10-Symfony-Internals[31/12/2010 02:34:18] The More with symfony book | Plonger dans les Entrailles de Symfony | symfony | Web PHP Framework Ce dernier étant un fichier de configuration YAML, il est par défaut stocké dans le cache. De plus, ce fichier de configuration peut déclarer un module comme étant interne (internal), en spécifiant le paramètre mod_yourModule_is_internal. Par conséquent, cela entraîne l'échec d'une requête car un module interne ne peut pas être appelé publiquement. Les modules internes étaient utilisés à l'origine pour générer du contenu de mail, grâce à la méthode getPresentationFor() par exemple. D'autres techniques existent aujourd'hui pour cela comme le rendu d'un template partiel à l'aide de la méthode renderPartial() d'une classe d'actions. Maintenant que le fichier module.yml est chargé, il est temps de vérifier une deuxième fois que le module courant est activé. En effet, à ce stade, il est possible de modifier la valeur de mod_$moduleName_enabled à false afin de désactiver le module. Comme il l'a été précisé, il y'a deux manières d'activer ou de désactiver un module. La différence est le résultat si le module est désactivé. Dans le premier cas, quand le paramètre sf_enabled_modules est vérifié, un module désactivé entrainera la levée d'une exception sfConfigurationException. Cette manière devrait être utilisée pour désactiver un module durablement. Dans le second cas, via le paramètre mod_$moduleName_enabled, un module désactivé entrainera un transfert de l'application vers le module désactivé. (voir le paramètre sf_module_disabled_module et sf_module_disabled_action ). Il est vivement recommandé d'utiliser cette manière lorsqu'il s'agit de >désactiver un module temporairement. La dernière opportunité de configurer un module réside dans le fichier config.php (apps/frontend/modules/yourModule/config/config.php) dans lequel du code PHP arbitraire peut être déposé avant son exécution dans le contexte de la méthode sfController::forward(). L'instance de la classe sfControler est disponible dans la variable $this dans la mesure où le code est exécuté dans la classe sfController. Résumé du Processus d'Expédition 1. sfFrontWebController::dispatch() est appelée 2. sfController::forward() est appelée 3. Vérification de l'existence du fichier generator.yml 4. Vérification de l'existence du module et de l'action 5. Récupération d'une liste de répertoires de contrôleurs 6. Récupération d'une instance de l'action 7. Chargement du module de configuration d'après le fichier module.yml et / ou config.php. La Chaîne de Filtres Maintenant que toute la configuration est prête, il est temps de commencer le véritable travail. Il s'agit, dans ce cas particulier, de l'exécution de la chaîne de filtrage. La chaine de filtres de symfony implémente un motif de conception connu sous le nom de chaîne de responsabilités. Il s'agit d'un motif simple mais puissant qui permet de déterminer si l'exécution de la chaîne doit continuer ou non pour chaque maillon de celle-ci. Chaque maillon de la chaîne est aussi capable de s'exécuter avant et après le reste de la chaîne d'exécution. La configuration de la chaîne de filtrage est tirée du fichier filters.yml du module courant, ce qui explique pourquoi une instance de l'action est nécessaire. L'ensemble des filtres de la chaîne et l'ordre dans lequel ils sont appelés, peuvent être modifiés. Il ne faut pas pas oublier que le filtre de rendu doit toujours être le premier dans la liste ; les explications sont données plus loin. La configuration des filtres est la suivante par défaut. rendering security cache execution rendering http://www.symfony-project.org/more-with-symfony/1_4/fr/10-Symfony-Internals[31/12/2010 02:34:18] The More with symfony book | Plonger dans les Entrailles de Symfony | symfony | Web PHP Framework security cache execution Il est fortement recommandé d'ajouter des filtres personnalisés entre les >filtres security et cache. Le Filtre de Sécurité Etant donné que le filtre de rendu attend que les autres filtres se terminent avant de réaliser quoi que soit, le premier filtre exécuté en réalité est le filtre de sécurité. Ce filtre s'assure que tout est correct en fonction de ce qui a été écrit dans le fichier de configuration security.yml. Plus précisément, le filtre se charge de rediriger un utilisateur non identifié vers le module et l'action de login. En revanche, si l'utilisateur n'a pas des droits d'accès suffisants pour le module et l'action demandés, le filtre transférera l'utilisateur vers le module secure. Il n'est pas inintéressant de remarquer que ce filtre est exécuté si la sécurité est activée pour l'action donnée. Le Filtre du Cache Le filtre de cache intervient ensuite. Ce filtre a la possibilité d'empêcher l'exécution de filtres futurs. En effet, si le cache est activé, et que les données nécessaires sont déjà disponibles, pourquoi vouloir chercher à exécuter l'action quand même ? Bien sûr, ceci ne fonctionnera que pour des pages qui peuvent être chargées en cache intégralement, ce qui n'est pas le cas de la majorité des pages. Ce filtre a toutefois une partie de code qui s'exécute après le filtre d'exécution et juste avant le filtre de rendu. Ce code permet de paramétrer les bons en-têtes de cache HTTP, et de placer les pages en cache si nécessaire, grâce à la méthode sfViewCacheManager::setPageCache() Le Filtre d'Exécution Enfin, le filtre d'exécution prend en charge l'exécution de la logique métier et gérera la vue associée. Tout commence quand le filtre vérifie le cache pour l'action courante. Bien sûr, si quelque chose se trouve dans le cache, l'exécution de l'action actuelle sera annulée et la vue Success sera quant à elle exécutée. Si l'action n'est pas trouvée dans le cache, alors il est temps d'exécuter la méthode preExecute() du contrôleur, et d'exécuter enfin l'action elle-même. Ceci est accompli par une d'instance de la classe d'action via un appel à sfActions::execute(). Cette méthode ne fait que très peu de choses car elle vérifie simplement que l'action peut être appelée, puis elle l'appelle. De retour dans le filtre, la méthode postExecute() de l'action est maintenant exécutée. La valeur de retour de l'action est très importante car elle déterminera la vue à exécuter. Par défaut, si aucune valeur de retour n'est trouvée, c'est la valeur sfView::SUCCESS qui sera retournée par défaut, ce qui correspond à la valeur Success et donc au template indexSuccess.php. Il reste une dernière étape avant la génération de la vue. Le filtre vérifie si deux valeurs de retour spéciales ont été retournées sfView::HEADER_ONLY et sfView::NONE. Chacune de ces deux valeurs réalise ce que leur nom suggère de faire : envoi des en-têtes HTTP seulement (géré par sfWebResponse::setHeaderOnly()) ou bien désactivation complète de la génération du template. Bien que les noms des vues par défaut de symfony soient ALERT, ERROR, INPUT, NONE et SUCCESS, le développeur a la possibilité de retourner n'importe quelle autre valeur qui lui convient. A partir du moment où l'on sait ce que l'on souhaite précisément obtenir comme rendu, alors c'est que l'on est prêt à plonger dans la dernière étape du processus : l'exécution de la vue courante. La première chose à faire consiste à récupérer un objet sfView à l'aide de la méthode sfController::getView(). Cet objet peut provenir de deux endroits différents. Tout d'abord, il faut savoir que pour chaque action spécifique, un objet de vue personnalisée peut être configuré http://www.symfony-project.org/more-with-symfony/1_4/fr/10-Symfony-Internals[31/12/2010 02:34:18] The More with symfony book | Plonger dans les Entrailles de Symfony | symfony | Web PHP Framework dans une classe nommée actionSuccessView ou bien module_actionSuccessView, et présente dans un fichier appelé apps/frontend/modules/module/view/actionSuccessView.class.php. On considère ici que le module et l'action demandés se nomment respectivement module et action. Sinon, c'est la classe définie dans la directive de configuration mod_module_view_class qui sera utilisée, alors que sa valeur par défaut est sfPHPView. Utiliser une classe de vue personnalisée permet d'exécuter du code spécifique à >la vue dans la méthode sfView::execute(). Par exemple, c'est typiquement à cet endroit précis que l'on peut imaginer l'initialisation d'un moteur de templates si l'on souhaite en utiliser un. Il existe trois modes de rendu possibles pour rendre la vue : 1. sfView::RENDER_NONE" : c'est l'équivalent de sfView::NONE, il annule toute tentative de rendu. 2. sfView::RENDER_VAR : peuple la présentation de l'action, qui est alors accessible depuis la méthode sfActionStackEntry::getPresentation() correspondante à son entrée dans la pile des appels. 3. sfView::RENDER_CLIENT, c'est le mode par défaut qui rend la vue et alimente le contenu de la réponse. En effet, le mode rendu est utilisé uniquement à travers la méthode sfController::getPresentationFor() qui retourne le rendu pour un module et une action donnés. Le Filtre de Rendu Ce chapitre touche presque à sa fin, mais il reste encore une dernière étape à franchir. A ce stade, la chaîne de filtrage a pratiquement terminé son exécution. Or, il ne faut pas oublier que le comportement de ce filtre est un peu particulier. En effet, il a attendu depuis le début que tous les filtres de la chaîne de filtres finissent leur tâche respective avant de pouvoir commencer la sienne. Le travail du filtre de rendu consiste à envoyer le contenu de la réponse au navigateur en utilisant la méthode sfWebResponse::send(). Résumé de l'Exécution de la Chaîne de Filtrage 1. La chaîne de filtrage est instanciée avec sa configuration contenue dans le fichier filters.yml 2. Le filtre de sécurité vérifie les autorisations et les droits d'accès 3. Le filtre de cache gère le cache pour la page courante 4. Le filtre d'exécution exécute l'action 5. Le filtre de rendu envoie la réponse générée à travers l'objet sfWebResponse Résumé général 1. Récupération de la configuration de l'application 2. Création d'une instance de sfContext 3. Initialisation de sfContext 4. Chargement des Factories 5. Notification des événements : 1. request.filter_parameters 2. routing.load_configuration 3. context.load_factories 6. Ajout des paramètres globaux des templates 7. Appel de la méthode sfFrontWebController::dispatch() 8. Appel de la méthode sfController::forward() 9. Vérification de l'existence fichier generator.yml 10. Vérification de l'existence du module et de l'action 11. Récupération d'une liste des répertoires des contrôleurs 12. Récupération d'une instance de l'action 13. Chargement du module de configuration à travers le fichier module.yml et / ou config.php http://www.symfony-project.org/more-with-symfony/1_4/fr/10-Symfony-Internals[31/12/2010 02:34:18] The More with symfony book | Plonger dans les Entrailles de Symfony | symfony | Web PHP Framework 14. La chaîne de filtres est instanciée avec sa configuration contenue dans le fichier filters.yml 15. Le filtre de sécurité vérifie les autorisations et les droits d'accès 16. Le filtre de cache gère le cache pour la page courante 17. Le filtre d'exécution exécute l'action 18. Le filtre de rendu envoie la réponse via l'objet sfWebResponse Conclusion C'est fini ! Le cycle de traitement d'une requête a été entièrement géré. La boucle a été bouclée et nous sommes maintenant prêt à affronter toutes les suivantes. Bien sûr, ce chapitre n'est qu'un aperçu de ce traitement, mais nous pourrions néanmoins écrire un livre entier sur les processus internes de symfony. Nous vous invitons d'ores et déjà à explorer le code source par vous-même - c'est et ce sera toujours le meilleur moyen d'apprendre les mécanismes internes d'un framework ou d'une librairie. « Tirer Profit de l'Héritage de Table avec Doctrine Windows et symfony » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/10-Symfony-Internals[31/12/2010 02:34:18] The More with symfony book | Windows et symfony | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Windows et symfony You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Laurent Bonnet Introduction About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Ce chapitre est un nouveau tutoriel qui couvre, étape par étape, l'installation, le déploiement et les tests fonctionnels du framework symfony sur une plateforme Windows Server 2008. Afin de préparer le déploiement sur Internet, le tutoriel peut être exécuté sur l'environnement d'un serveur dédié hébergé sur Internet. Bien sûr, il est possible de compléter ce tutoriel sur un serveur local, ou bien sur une machine virtuelle installée sur le poste de travail du lecteur. Support symfony! Buy this book or donate. Les Raisons d'un Nouveau Tutoriel Aujourd'hui, il existe seulement deux sources d'informations en rapport à Microsoft Internet Information Server (IIS) sur le site officiel de symfony. Cependant, ces deux sources se rapportent à des versions précédentes qui n'ont plus évolué avec les nouvelles versions des systèmes d'exploitation de Microsoft Windows, particulièrement avec Windows Server 2008 (sorti en février 2008), qui inclut de nombreux changements intéressants pour les développeurs PHP : IIS version 7, la version embarquée dans Windows Server 2008, a été entièrement réécrite en vue d'une architecture complètement modulaire. IIS 7 a prouvé qu'il était très fiable, avec quelques correctifs intégrés dans Windows Update depuis le lancement du produit. IIS 7 intègre également l'accélérateur FastCGI, un pool d'applications multi-processus qui tire profit du modèle natif de processus léger des systèmes d'exploitation de Windows. L'implémentation de FastCGI de PHP améliore de 5 à 10 fois les performances à l'exécution, sans cache, comparé aux déploiements traditionnels ISAPI ou CGI de PHP sur Windows et IIS. Plus récemment, Microsoft a levé le voile sur un accélérateur PHP, qui est encore à l'état de Release Candidate à l'heure où ces lignes sont écrites. Extension Prévue pour ce Tutoriel Une section supplémentaire de ce chapitre est en cours d'élaboration et sera publiée rapidement sur le site officiel du projet symfony après la publication de cet ouvrage. Elle couvre la connexion à MS SQL Server via PDO et Microsoft prévoit à l'heure actuelle des améliorations à venir à ce sujet. [PHP_PDO_MSSQL] extension=php_pdo_mssql.dll Pour l'instant, les meilleures performances à l'exécution du code sont obtenues en utilisant le connecteur PHP 5 natif de SQL Server, un connecteur open-source disponible sur Windows dans sa version 1.1. Il est implémenté sous forme d'une nouvelle extension DLL pour PHP: [PHP_SQLSRV] extension=php_sqlsrv.dll Il est possible d'utiliser aussi bien Microsoft SQL Server 2005 ou 2008 pour la base de données. L'extension prévue pour ce tutoriel couvrira l'utilisation de l'édition gratuite : SQL Server Express. Comment Faire Fonctionner ce Tutoriel sur Différents Systèmes Windows (32 bits inclus) Chapter Content Introduction Les Raisons d'un Nouveau Tutoriel Comment Faire Fonctionner ce Tutoriel sur Différents Systèmes Windows (32 bits inclus) Serveur Web utilisé tout au long du Document Bases de Données Configuration de Windows Server Vérifications Préliminaires Serveur Dédiés sur Internet Installer PHP - Quelques Clics Suffisent Exécuter PHP depuis l'Interface en Ligne de Commande (CLI) Installation et Usage de la Sandbox Symfony Préparer le Terrain pour symfony http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Ce document a été écrit spécifiquement pour les éditions 64 bits de Windows Server 2008. Néanmoins, le lecteur sera capable d'utiliser d'autres versions sans complication supplémentaire. La version exacte du système d'exploitation utilisé dans les captures d'écran est Windows Server 2008 Enterprise Edition accompagnée d'un Service Pack 2 pour du matériel 64 bits. Version 32 bits de Windows Ce tutoriel est aisément portable sur des versions 32 bits de Windows, en remplaçant les références suivantes dans le texte : Sur les éditions 64-bits : C:\Program Files (x86)\ et C:\Windows\SysWOW64\ Sur les éditions 32-bits : C:\Program Files\ et C:\Windows\System32\ Test de l'Exécution Création d'une Application Web Sandbox : Configuration Web du Front-End Création d'un nouveau Projet symfony Télécharger, Créer un Répertoire et Copier les Fichiers Définition de l'Arborescence du Projet Création et Initialisation Création d'une Application Web Configuration des Applications Prêtes pour Internet A Propos des Versions non Enterprise De plus, si le serveur n'utilise pas une version Enterprise, ce n'est pas un problème. Ce document est directement portable pour d'autres versions de logiciel Windows Server : Windows Server 2008 Web, Standard ou Datacenter Windows Server 2008 Web, Standard ou Datacenter avec Service Pack 2 Windows Server 2008 R2 Web, Standard, Enterprise ou Datacenter. Be trained by symfony experts Jan 24: Paris Feb 21: Paris Il est important de noter que toutes les éditions de Windows Server 2008 RC2 sont seulement disponibles pour les systèmes d'exploitation 64 bits. Les paramètres de configuration régionale utilisés dans ces captures d'écran sont en-US mais un pack d'internationalisation pour le Français aurait pu aussi être installé. (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris A Propos des Versions Internationales (Maîtrise de & Doctrine - Français) (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Il est également possible d'exécuter ce tutoriel sur un système d'exploitation Windows client : Windows XP, Windows Vista et Windows Seven, en mode x64 ou x86. Search Serveur Web utilisé tout au long du Document powered by google Le serveur web utilisé ici est Microsoft Internet Information Server dans sa version 7.0, qui est inclus comme un rôle dans toutes les versions de Windows Server 2008. Ce tutoriel s'appuie sur un serveur Windows Server 2008 entièrement fonctionnel tandis qu'IIS sera installé "from scratch". Les étapes d'installation utilisent les choix par défaut bien que deux autres modules spécifiques seront ajoutés : FastCGI et URL Rewrite. Ces derniers interviennent notamment dans l'architecture modulaire de IIS 7.0. Bases de Données SQLite est la base de données pré-configurée dans un bac à sable symfony. Sur Windows, il n'y a rien de plus à installer particulièrement. En effet, le support de SQLite est directement implémenté dans l'extension PHP PDO pour SQLite, qui est d'ailleurs installée par défaut avec PHP. Par conséquent, il n'est pas nécessaire de télécharger et d'exécuter une instance séparée de SQLITE.EXE. [PHP_PDO_SQLITE] extension=php_pdo_sqlite.dll Configuration de Windows Server Il est conseillé d'utiliser une installation récente de Windows Server dans le but de faire correspondre les captures d'écran aux étapes de ce chapitre avec l'écran du lecteur. Bien sûr, travailler directement sur une machine existante est concevable bien que des différences puissent subsister à cause du système d'exploitation installé, de l'exécution ou bien des configurations régionales. Afin d'obtenir les mêmes copies d'écran présentées dans ce tutoriel, il est recommandé au lecteur de se procurer un Windows Server dédié sur un environnement virtuel, disponible gratuitement sur Internet pour une période de 30 jours. Comment obtenir un essai gratuit à Windows Server? Il est bien sûr possible d'utiliser n'importe quel serveur dédié avec un accès à Internet. Un serveur physique ou un serveur virtuel dédié (VDS) fera largement l'affaire. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Un serveur limité 30 jours avec Windows est disponible à l'essai grâce à Ikoula, un hébergeur web français qui offre une liste complète de services pour les développeurs et les graphistes. Cet essai gratuit démarre bien sûr à 0 € par mois pour une machine virtuelle Windows qui tourne sur un environnement Microsoft Hyper-V. Par ailleurs, une machine virtuelle entièrement fonctionnelle, intégrant une version Windows Server 2008 Web, Standard, Entreprise ou Datacenter, peut être mise à disposition gratuitement pendant une période de 30 jours. Pour commander, il suffit de se connecter au site http://www.ikoula.com/flex_server et de cliquer sur le bouton "Testez gratuitement". Afin d'obtenir les mêmes messages décrits dans ce document, le système d'exploitation commandé avec le serveur Flex est : "Windows Server 2008 Enterprise Edition 64 bits". Il s'agit d'une distribution x64, livrée avec les deux locales fr-FR et en-US. Il est donc très facile de passer de fr-FR à en-US et vice-versa depuis le panneau de commande "Windows Control Panel". Plus précisément, ce paramètre se trouve dans "Regional and Language Options", situées sous l'onglet "Keyboards and Languages". Il suffit de cliquer sur "Install/uninstall languages". Il est impératif de posséder les accès Administrateur sur le serveur. Si le lecteur travaille depuis une station de travail distante, il devra alors exécuter les Remote Desktop Services, plus connus sous le nom de Terminal Server Client en anglais, et s'assurer qu'il dispose des droits Administrateur. La distribution utilisée ici est Windows Server 2008 accompagnée du Service Pack 2. Windows Server 2008 a été installé avec l'environnement graphique, qui correspond au thème de Windows Vista. Il est également possible d'utiliser la ligne de commande uniquement sur les http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework versions de Windows Server 2008 intégrant les mêmes services dans le but de réduire le poids de la distribution (1.5 Go au lieu de 6.5 Go). Cela réduit aussi les attaques en surface et le nombre de correctifs Windows Update qui ont besoin d'être appliqués. Vérifications Préliminaires - Serveur Dédiés sur Internet Maintenant que le serveur est directement accessible depuis Internet, c'est aussi une bonne idée de vérifier que le pare-feu de Windows fournit une protection résidente active. Les seules exceptions à vérifier sont les suivantes : Core Networking Remote Desktop, (si atteint à distance) Secure World Wide Web Services (HTTPS) World Wide Web Services (HTTP) Ensuite, il est toujours bon d'exécuter Windows Update afin de s'assurer que toutes les parties du système d'exploitation sont à jour avec les derniers correctifs, patches et documentation. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework En guise de dernière étape de préparation, et dans un souci de supprimer toutes les paramètres conflictuels potentiels de la configuration existante de Windows ou de IIS, il est recommandé de désinstaller le rôle Web du serveur Windows s'il était précédemment installé. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Installer PHP - Quelques Clics Suffisent A présent, IIS et PHP peuvent être installés en une opération triviale. PHP n'est PAS une partie de la distribution Windows Server 2008, par conséquent, il faut tout d'abord installer Microsoft Web Platform Installer 2.0, abrégé Web PI dans les prochaines sections. Web PI prend le soin d'installer toutes les dépendances nécessaires pour exécuter PHP sur n'importe quel système Windows / IIS. Par conséquent, il déploie IIS avec les Services de Rôle (Role Service) minimaux pour le Serveur Web (Web Server), et fournit les options minimales pour l'exécution de PHP. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework L'installation de Microsoft Web Platform Installer 2.0 contient un analyseur de configuration qui vérifie les modules existants, propose des modules de mises à jour nécessaires, et qui permet aussi de bêta-tester des extensions non publiées de la plateforme Microsoft Web Platform. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Web PI 2.0 offre une installation de PHP en un seul clic. La sélection installe l'implémentation Win 32 "non-thread safe" de PHP, qui est la meilleure associée à IIS 7 et FastCGI. Il offre également le binaire PHP le plus récent testé : ici 5.2.11. Pour le trouver, il suffit de sélectionner l'onglet "Frameworks and Runtimes" sur la gauche. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Après avoir choisi PHP, Web PI 2.0 sélectionne automatiquement toutes les dépendances nécessaires au service des pages web .php stockées sur le serveur, y compris les services minimaux de rôles de IIS 7.0: http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Ensuite, il suffit de cliquer sur "Install" puis de sélectionner le bouton "I Accept". L'installation des composants IIS commence, tandis que, en parallèle, le binaire de PHP est téléchargé et quelques modules sont mis à jour. Une mise à jour de IIS 7.0 FastCGI par exemple. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Enfin, le programme de configuration de PHP s'exécute, et, après quelques minutes devrait afficher un écran similaire à la capture ci-dessous : http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Il ne reste alors plus qu'à cliquer sur "Finish". Windows Server écoute à présent et est capable de répondre sur le port 80. La capture d'écran ci-dessous atteste de son bon fonctionnement. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Maintenant, pour vérifier que PHP est correctement installé et disponible depuis IIS, il suffit de créer un petit fichier phpinfo.php accessible sur le port 80 du serveur web par défaut. Ce fichier doit être créé dans le répertoire C:\inetpub\wwwroot. Avant de faire cela, il convient de s'assurer que toutes les extensions des fichiers sont visibles dans l'Explorateur de Windows. Pour ce faire, il suffit de sélectionner l'option "Unhide Extensions for Known Files Types" dans les paramètres de l'explorateur. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework La nouvelle étape consiste à ouvrir l'explorateur Windows afin de se rendre dans le dossier C:\inetpub\wwwroot. Puis, un clic droit sur l'option "New Text Document" permet de créer un nouveau fichier et de le renommer avec le nom phpinfo.php, avant de copier l'appel à la fonction PHP phpinfo() en guise de contenu. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Ensuite, il ne reste plus qu'à vérifier l'exécution du fichier dans le navigateur en ajoutant /phpinfo.php à la fin de l'URL du serveur. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Enfin, pour s'assurer que symfony s'installera sans aucun problème, il suffit de télécharger le fichier http://sf-to.org/1.3/check.php et de l'exécuter. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Ce fichier doit alors être copié dans le même répertoire que phpinfo.php (C:\inetpub\wwwroot) et renommé en check_configuration.php si nécessaire. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Enfin, il ne reste plus qu'à réouvrir le navigateur une dernière fois et d'ajouter /check_configuration.php à la fin de l'URL du serveur comme le montre la capture d'écran cidessous. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Exécuter PHP depuis l'Interface en Ligne de Commande (CLI) Afin de pouvoir exécuter plus tard des tâches symfony en ligne de commande, il est nécessaire de s'assurer que PHP.EXE est accessible depuis l'invité de commande et qu'il s'exécute correctement. Pour ce faire, il suffit d'ouvrir un invité de commande, de se positionner dans le répertoire C:\inetpub\wwwroot et enfin de taper : PHP phpinfo.php Le message d'erreur suivant devrait apparaître à l'écran : http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Si rien n'est fait, l'exécution de PHP.EXE tiendra du fait de l'absence du fichier MSVCR71.DLL. Ce fichier DLL doit être récupéré puis installé au bon endroit. Le fichier MSVCR71.DLL est une ancienne version de Microsoft Visual C++ qui date d'avant 2003. Il est donc intégré dans le paquet redistribuable du Framework .Net 1.1. Le paquet redistribuable du Framework .Net 1.1 est téléchargeable depuis le site MSDN Le fichier recherché est installé dans le répertoire suivant : C:\Windows\Microsoft.NET\Framework\v1.1.4322 Il suffit de copier le fichier MSVCR71.DLL dans le répertoire ci-dessous : sur les systèmes x64 : le répertoire C:\windows\syswow64 ; sur les systèmes x86 : le répertoire C:\windows\system32. A présent, le Framework .Net 1.1 peut être désinstallé. L'exécutable PHP.EXE peut quant à lui être exécuté depuis l'invité de commande sans erreur. Par exemple : PHP phpinfo.php PHP check_configuration.php Plus tard dans ce chapitre, il s'agira de vérifier que le fichier SYMFONY.BAT - correspondant à la syntaxe de la commande symfony - de la distribution "sandbox" provoque lui aussi la réponse attendue. Installation et Usage de la Sandbox Symfony http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Le paragraphe suivant est un résumé du tutoriel officiel "Getting Started with symfony" au sujet de "la Sandbox" : La sandbox, bac à sable en français, est un projet symfony pré-configuré et très facile d'installation, qui fonctionne en l'état avec des paramètres de configuration prédéfinis par défaut. C'est un excellent moyen de tester symfony sans pour autant se soucier d'une installation propre qui respecte les bonnes pratiques web. La sandbox est pré-configurée à être utilisée avec SQLite en guise de moteur de base de données. Sur Windows, il n'y a rien de particulier à installer puisque le support de SQLite est directement implémenté dans l'extension PDO de PHP pour SQLite. Cette extension PDO est installée par défaut au moment de l'installation de PHP. Cette tâche a d'ores et déjà été accomplie plus tôt lorsque l'environnement d'exécution PHP a été installé via l'outil Microsoft Web PI. A ce stade, il convient de vérifier que l'extension SQLite est correctement installée et référencée dans le fichier PHP.INI, qui réside dans le répertoire C:\Program Files (x86)\PHP. De plus, il faut s'assurer que le support de SQLite est activé en vérifiant que le connecteur PDO DLL adéquat est bien défini à la valeur C:\Program Files (x86)\PHP\ext\php_pdo_sqlite.dll. Préparer le Terrain pour symfony Le projet initialisé dans le bac à sable de symfony est "prêt à être installé et exécuté". Il se présente sous la forme d'une archive .zip. L'archive doit tout d'abord être téléchargée puis extraite dans un emplacement temporaire, comme le répertoire "Downloads", disponible en lecture et écriture dans le répertoire C:\Users\Administrator. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Il s'agit à présent de créer un répertoire pour la destination finale du bac à sable, comme le dossier F:\dev\sfsandbox. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Ensuite, tous les fichiers doivent être sélectionnés - CTRL-A - dans l'explorateur Windows depuis l'emplacement de téléchargement (source), puis copiés dans le répertoire F:\dev\sfsandbox nouvellement créé. Ce n'est pas moins de 2599 objets qui sont copiés dans le répertoire final. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Test de l'Exécution Il convient maintenant de tester que l'installation du projet a réussi. Pour ce faire, il suffit d'ouvrir un nouvel invité de commande, puis de se positionner dans le répertoire F:\dev\sfsandbox afin d'exécuter la commande suivante. PHP symfony -V L'exécution de cette commande devrait retourner ceci : symfony version 1.3.0 (F:\dev\sfsandbox\lib\symfony) Depuis le même terminal de commande, la commande suivante peut aussi être exécutée : SYMFONY.BAT -V Cette commande retourne aussi le même résultat : symfony version 1.3.0 (F:\dev\sfsandbox\lib\symfony) http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Création d'une Application Web La création d'une application web sur le serveur local nécessite l'utilisation du gestionnaire de IIS7, qui est panneau de contrôle utilisateur graphique pour toutes les activités relatives à IIS. Toutes les actions déclenchées depuis cette interface graphique sont pour le moment exécutées en coulisses via l'interface en ligne de commande. La console d'administration de IIS est accessible depuis le menu Start dans Programs, Administrative Tools, Internet Information Server (IIS) Manager. Reconfigurer le Site Web par Défaut pour Eviter les Conflits sur le Port 80 Il s'agit maintenant de s'assurer que seule la sandbox symfony est capable de répondre sur le port 80 (HTTP). Pour y parvenir, le paramètre "Default Web Site" existant doit être modifié afin d'écouter sur le port 8080. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Si le pare-feu de Windows est activé, alors une exception pour le port 8080 devra être créée manuellement afin que la requête puisse être capable d'atteindre le "Default Web Site". Pour ce faire, il suffit de se rendre dans le panneau de contrôle de Windows, puis de sélectionner le pare-feu Windows, de cliquer sur "Allow a program through Windows Firewall", et enfin de cliquer sur "Add port" pour autoriser cette exception. Il ne reste plus qu'à cocher la case pour l'activer après sa création. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Ajouter un nouveau Site Web pour la Sandbox L'étape suivante consiste à ouvrir IIS Manager depuis le menu "Administration Tools". Dans le panneau de gauche, il convient de sélectionner l'icône "Sites" puis de faire un clic droit dessus et de choisir "Add Web Site" dans le menu ouvert. A présent, un nom doit être spécifié pour le site, par exemple "Symfony Sandbox" ainsi qu'un chemin physique, D:\dev\sfsandbox. Les autres champs peuvent rester tels qu'ils sont. La boîte de dialogue devrait ressembler à celle de la capture ci-dessous. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Après un clic sur OK, si un petit x apparaît sur l'icône du site web (dans Feature View / Sites), alors il ne faut pas hésiter à cliquer sur "Restart" dans le panneau de droite pour le faire disparaître. Vérifier si le Site Web Répond Depuis IIS Manager, il s'agit maintenant de sélectionner le site "Symfony Sandbox", et, dans le panneau de droite, de cliquer sur "Browse *.80 (http)". http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Bien que cela ne soit pas prévu, un message d'erreur explicite devrait apparaître à l'écran : HTTP Error 403.14 - Forbidden. Le serveur web est par défaut configuré pour empêcher le listage du contenu d'un répertoire. Il s'agit de la configuration par défaut du serveur web qui spécifie que le contenu de ce répertoire ne doit pas être listé. Comme aucun fichier index.php ou index.html n'existe dans le répertoire D:\dev\sfsandbox, le serveur retourne normalement le message d'erreur "Forbidden". Il n'y a donc aucune raison de s'inquiéter. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Maintenant, en remplaçant http://localhost par http://localhost/web dans la barre d'adresse du navigateur, ce dernier, Internet Explorer par défaut, devrait afficher "Symfony Project Created" comme le montre la capture ci-dessous. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Par ailleurs, il se peut qu'une barre jaune clair apparaisse en haut de la fenêtre indiquant "Intranet settings are now turned off by default". Cette alerte indique seulement que les paramètres de configuration Intranet sont moins sécurisés que les paramètres Internet. Là encore, il n'y a aucune raison de s'inquiéter et les options de l'alerte peuvent être sélectionnées en toute sécurité. Pour fermer cette alerte de manière permanente, il suffit de faire un clic droit dessus, puis de sélectionner l'option appropriée. Cet écran confirme que le fichier index.php par défaut a été correctement chargé depuis le chemin D:\dev\sfsandbox\web\index.php, puis exécuté et que les bibliothèques de symfony sont normalement configurées. Il ne reste maintenant plus qu'une dernière tâche à réaliser avant de pouvoir véritablement jouer avec la sandbox de symfony : configurer la page web de l'application front-end en important les règles de réécriture d'URL. Ces règles sont implémentées dans les fichiers .htaccess et peuvent être contrôlées en quelques clics depuis IIS Manager. Sandbox : Configuration Web du Front-End Il s'agit maintenant de configurer l'application front-end de la sandbox dans le but de commencer à jouer avec les véritables fonctionnalités de symfony. Par défaut, la page front-end peut être atteinte et exécutée correctement lorsqu'elle est appelée depuis la machine locale (par exemple, avec le nom localhost ou l'adresse 127.0.0.1). http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Pour s'assurer que la sandbox est complètement fonctionnelle sur Windows Server 2008, il est possible d'explorer la configuration, les logs et les horodateurs dans les panneaux de la barre de débogage en haut à droite de l'écran. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Bien que l'application sandbox puisse être accessible depuis Internet ou bien depuis une adresse IP locale, la sandbox est principalement structurée comme un outil d'apprentissage du framework symfony sur une machine locale. Par conséquent, ce chapitre couvrira des détails relatifs à l'accès distant dans une prochaine section. Création d'un nouveau Projet symfony Créer un environnement de projet symfony pour de réelles intentions de développement est presque aussi simple qu'une installation à partir de la sandbox. Les lignes qui suivent décrivent le processus d'installation complet à partir d'une procédure simplifiée, dans la mesure où elle est équivalente à l'installation et au déploiement d'une sandbox. La différence réside dans le fait que, pour cette section du "projet", c'est la configuration de l'application web qui est concernée afin de la faire fonctionner depuis n'importe où sur Internet. Comme la sandbox, le projet symfony est pré-configuré par défaut pour utiliser SQLite en guise de moteur de base de données. Tout ceci a été installé et configuré plus tôt dans ce chapitre. Télécharger, Créer un Répertoire et Copier les Fichiers Chaque version de symfony peut être téléchargée sous forme d'une archive .zip et ensuite utilisée pour créer un projet depuis zéro. Il suffit, pour ce faire, de télécharger l'archive contenant les bibliothèques depuis le site officiel de symfony. Ensuite, le contenu de l'archive peut alors être extrait dans une destination temporaire, comme le répertoire "downloads". http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework A ce stade, l'objectif est de créer une arborescence complète pour la destination finale du projet. Cette étape est un peu plus difficile qu'avec la sandbox. Définition de l'Arborescence du Projet Cette nouvelle section s'intéresse à la création d'une arborescence de fichiers pour le projet. Pour commencer, il convient de se positionner dans le volume racine, D: par exemple. Puis, un nouveau répertoire \dev doit être créé sur D: dans lequel il faut également créer un sousrépertoire sfproject. D: MD CD MD CD dev dev sfproject sfproject Le pointeur de fichier se situe maintenant dans le répertoire D:\dev\sfproject. A partir de cet emplacement, les sous-répertoires lib, vendor et symfony peuvent être créés en cascade. MD CD MD CD MD CD lib lib vendor vendor symfony symfony Le pointeur de fichier est à présent dans D:\dev\sfproject\lib\vendor\symfony. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Tous les fichiers doivent maintenant être sélectionnés (CTRL-A dans l'explorateur Windows) depuis l'emplacement de téléchargement (source), puis copiés depuis Downloads dans D:\dev\sfproject\lib\vendor\symfony. Ce sont environ 3819 fichiers qui ont été ainsi copiés dans le répertoire de destination finale. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Création et Initialisation Il est temps d'ouvrir un nouvel invité de commande, puis de se positionner dans le répertoire D:\dev\sfproject juste avant d'exécuter la commande suivante. PHP lib\vendor\symfony\data\bin\symfony -V Celle-ci devrait retourner le résultat ci-dessous : symfony version 1.3.0 (D:\dev\sfproject\lib\vendor\symfony\lib) L'exécution de la commande PHP ci-après suffit à elle-même pour initialiser un nouveau projet : PHP lib\vendor\symfony\data\bin\symfony generate:project sfproject Le résultat de son exécution provoque à l'écran l'affichage d'une suite d'opérations sur des fichiers comprenant quelques commandes chmod 777. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Toujours à l'intérieur de l'invité de commande, il s'agit maintenant de créer une nouvelle application symfony en exécutant la commande suivante. PHP lib\vendor\symfony\data\bin\symfony generate:app sfapp Là encore, le résultat consiste en une liste d'opérations sur les fichiers incluant des commandes chmod 777. D'ici, au lieu de taper PHP lib\vendor\symfony\data\bin\symfony chaque fois que c'est nécessaire, il suffit de copier le fichier symfony.bat de son point d'origine dans le répertoire racine du projet. copy lib\vendor\symfony\data\bin\symfony.bat Maintenant, il existe une commande plus commode à exécuter dans l'invité en ligne de commande depuis le répertoire D:\dev\sfproject. Toujours dans le répertoire D:\dev\sfproject, la nouvelle commande peut être exécutée. symfony -V afin d'obtenir la réponse traditionnelle attendue : symfony version 1.3.0 (D:\dev\sfproject\lib\vendor\symfony\lib) Création d'une Application Web Dans les lignes suivantes, le lecteur est supposé avoir lu les étapes préliminaires de création d'une application web de sandbox pour reconfigurer le site web par défaut ("Default Web Site"), afin qu'il n'interfère pas avec le port 80. Ajout d'un nouveau Site Web pour ce Projet http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Il s'agit maintenant d'ouvrir IIS Manager depuis les outils d'administration (Administration Tools) de Windows. Dans le panneau de gauche, il convient de sélectionner l'icône "Sites" puis d'effectuer un clic droit afin de choisir la valeur "Add Web Site" dans la nouvelle fenêtre de menu. Ensuite, le lecteur est invité à saisir un nom de site, par exemple "Symfony Project", ainsi qu'un chemin physique absolu, D:\dev\sfproject. Les autres champs, quant à eux, peuvent rester tels quels et la boîte de dialogue ci-dessous devrait alors faire son apparition. Après avoir cliqué sur OK, si une petite croix, x, apparaît sur l'icône du site web (dans Features View / Sites). Il ne faut pas hésiter à cliquer sur "Restart" dans le panneau de droite pour la faire disparaître. Vérifier si le Site Web Répond Depuis IIS Manager, il s'agit de sélectionner le site "Symfony Project", et, dans le panneau de droite, de cliquer "Browse *.80 (http)". Le même message explicite obtenu au moment de la configuration de la sandbox devrait apparaître à l'écran : HTTP Error 403.14 - Forbidden Le serveur Web est toujours configuré pour ne pas afficher le contenu du répertoire. En tapant http://localhost/web dans la barre d'adresse du navigateur, une nouvelle page indiquant "Symfony Project Created" devrait apparaître. Cette dernière est légèrement différente de celle précédemment obtenue avec la sandbox dans la mesure où elle ne contient aucune image. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Les images ne sont pas présentes pour le moment, bien qu'elles existent dans le répertoire /sf dans les librairies de symfony. Il est facile de les relier au répertoire /web en ajoutant un répertoire virtuel dans /web, intitulé sf, et pointant vers D:\dev\sfproject\lib\vendor\symfony\data\web\sf. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Désormais, le projet dispose de sa propre page d'accueil par défaut "Symfony Project Created" avec toutes les images chargées. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Et enfin, l'application symfony entière fonctionne. Pour s'en convaincre, il suffit de taper l'url de l'application web, http://localhost/web/sfapp_dev.php, dans le navigateur web. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Il ne reste alors plus qu'à effectuer un test en local : vérifier la configuration, les logs et les horodateurs dans les panneaux de la barre de débogage en haut de l'écran pour s'assurer que le projet est entièrement fonctionnel. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Configuration des Applications Prêtes pour Internet Le projet symfony générique fonctionne désormais localement comme la sandbox depuis le serveur local, situé à l'adresse http://localhost ou à l'adresse IP http://127.0.0.1. Maintenant, il est temps de rendre l'application accessible au grand public sur Internet. La configuration par défaut du projet protège l'application d'être exécutée depuis un emplacement distant, bien qu'en réalité, elle devrait être autorisée à accéder aux deux fichiers index.php et sfapp_dev.php. Il s'agit donc d'exécuter le projet depuis un serveur web, en utilisant l'adresse IP externe du serveur (par exemple 94.125.163.150) et le FQDN du serveur dédié virtuel (par exemple 12543hpv163150.ikoula.com). Il est également possible d'utiliser les deux adresses depuis l'intérieur du serveur, dans la mesure où elle ne sont pas reliées à la boucle locale 127.0.0.1. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework L'accès aux fichiers index.php et sfapp_dev.php depuis un emplacement distant est désormais fonctionnel comme il l'a été expliqué avant. En revanche, l'exécution du fichier sfapp_dev.php échoue dans la mesure où il n'est pas autorisé par défaut. Cela empêche des utilisateurs malicieux d'accéder à l'environnement de développement qui contient des informations potentiellement sensibles à propos du projet. Le fichier sfapp_dev.php peut être édité pour le faire fonctionner depuis un emplacement distant mais c'est très fortement déconseillé pour des raisons évidentes de sécurité. Ce fichier ne devrait d'ailleurs jamais être déployé sur un serveur de production. Enfin, il ne reste plus qu'à simuler un véritable nom de domaine en éditant le fichier hosts. Ce fichier s'occupe de résoudre le nom local FQDN sans avoir besoin d'installer un serveur DNS sur Windows. Le service DNS est disponible sur toutes les versions de Windows Server 2008 R2, et aussi dans Windows Server 2008 Standard, Enterprise et Datacenter. Sur les systèmes d'exploitation x64, le fichier hosts est situé par défaut dans le répertoire C:\Windows\SysWOW64\Drivers\etc. Le fichier hosts est pré-rempli afin de résoudre le nom de domaine local localhost vers l'adresse IP 127.0.0.1, en IPv4 et ::1 en IPv6. Pour finir, un faux nom de domaine, tel que sfwebapp.local et résolu localement peut être ajouté au fichier. http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Le projet symfony s'exécute à présent sur le Web, sans aucun DNS, depuis une session du navigateur web exécutée sur le serveur web. « Plonger dans les Entrailles de Symfony Développer pour Facebook » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Windows et symfony | symfony | Web PHP Framework Pirum - PEAR channel server Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/11-Windows-and-Symfony[31/12/2010 02:34:54] The More with symfony book | Développer pour Facebook | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Développer pour Facebook You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Fabrice Bernhard Facebook, avec plus de 350 millions de membres aujourd'hui, est devenu le réseau social de référence sur Internet. L'une de ses principales qualités est la mise à disposition de la plate-forme Facebook, une API qui permet aux développeurs de créer des applications directement dans le site Facebook mais aussi de connecter d'autres sites Internet avec le système d'authentification et le graphe social de Facebook. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Comme le site Facebook est écrit en PHP, il n'est pas étonnant que la Buy this book or donate. librairie client officielle qui permette d'utiliser l'API soit aussi développée en PHP. Cela fait de facto de symfony un choix logique pour développer rapidement et proprement des applications Facebook ou bien des sites Facebook Connect. Mais les nombreuses fonctionnalités de symfony permettent de s'adapter particulièrement bien à la programmation pour Facebook et de gagner ainsi en temps et en qualité. Ce chapitre couvre les spécificités de la programmation Facebook avec symfony. Après un rapide résumé de l'API Facebook et de son utilisation, les sections suivantes expliqueront comment utiliser symfony au mieux en développant pour Facebook et comment bénéficier des contributions de la communauté au travers du plugin sfFacebookConnectPlugin. Il s'agira d'illustrer les notions acquises au travers d'une simple application "Hello you!", avant de dévoiler quelques astuces pour résoudre les problèmes les plus courants de développement avec Facebook. Développer pour Facebook Bien que l'API soit globalement la même, il existe deux cas d'utilisation très différents. Le premier concerne la création d'une application Facebook dans le site Facebook.com tandis que la seconde consiste à implémenter Facebook Connect sur un site web externe. Les Applications Facebook Les applications Facebook sont des applications web contenues dans Facebook. Leur principal atout réside dans le fait qu'elles sont directement incluses dans le site communautaire Facebook et son réseau social de plus de 300 millions de personnes, permettant ainsi à n'importe quelle application virale de se propager à une vitesse incroyable. Farmville est l'exemple le plus récent et le plus impressionnant, avec plus de 60 millions d'utilisateurs actifs chaque mois et plus de 2 millions de fans convertis en quelques mois ! C'est l'équivalent de la population française qui revient chaque mois sur cette application pour travailler sur leur ferme virtuelle. Les applications Facebook interagissent avec le site Facebook et son réseau social sous différentes formes. Voici un bref aperçu des endroits où l'application Facebook pourra apparaître. Le Canevas (Canvas) Le canevas est habituellement la partie principale de l'application. C'est un petit site intégré dans le cadre du site Facebook. L'Onglet de Profil (Profile Tab) L'application peut aussi être contenue dans un onglet du profil d'un utilisateur ou d'une page de fan. Les principales contraintes sont alors les suivantes : une page seulement. Il est impossible de définir des liens directs vers d'éventuelles souspages de l'onglet. pas d'interaction dynamique au démarrage, que ce soit flash ou JavaScript. Pour proposer des fonctionnalités dynamiques, l'application doit attendre une interaction de l'utilisateur, un clic sur un lien ou un bouton par exemple. http://www.symfony-project.org/more-with-symfony/1_4/fr/12-Developing-for-Facebook[31/12/2010 02:35:04] Chapter Content Développer pour Facebook Les Applications Facebook Facebook Connect Configurer un Premier Projet avec le Plugin sfFacebookConnectPlugin Créer l'Application sur Facebook Installer et Configurer sfFacebookConnectPlugin Configurer une Application Facebook Configurer un Site Facebook Connect Connecter sfGuard avec Facebook Choisir entre FBML et XFBML : Problème Résolu par symfony L'Application de Démonstration "Hello You" Facebook Connect Comment Facebook Connect The More with symfony book | Développer pour Facebook | symfony | Web PHP Framework La Boîte de Profil (Profile Box) C'est un reste de l'ancienne version de Facebook, qui est devenue quelque peu obsolète. La boîte de profil permet d'afficher quelques informations dans un widget rectangulaire que l'on retrouve dans l'onglet "Boîtes" du profil d'un utilisateur. L'Onglet Informations Certaines informations statiques relatives à l'utilisateur et l'application peuvent être affichées dans l'onglet "Informations" du profil. Ces informations apparaitront juste sous l'âge de l'utilisateur, son adresse et son court CV. Publication dans le Flux d'Actualités L'application peut publier des actualités, liens, photos, vidéos dans le flux d'actualités, sur le mur d'un ami ou bien directement modifier le statut de l'utilisateur. La Page d'Informations C'est la page de profil de l'application, créée automatiquement par Facebook. C'est ici que le créateur de l'application peut interagir avec ses utilisateurs. Cette page concerne plus particulièrement l'équipe marketing que l'équipe de développement. fonctionne et Différentes Stratégies d'Intégration Le Filtre Facebook Connect Implémentation Propre afin d'Eviter l'Erreur Fatale d'IE Bonnes Pratiques pour les Applications Facebook Configurer Plusieurs Serveurs Facebook Connect de Test Utiliser le Système de Log de symfony pour Déboguer le FBML Eviter les Mauvaises Redirections Facebook avec un Proxy Utiliser le Helper fb_url_for() dans les Applications Facebook Rediriger dans une Application Facebook Connecter des Utilisateurs Existants avec leur Compte Facebook Aller Plus Loin Facebook Connect Facebook Connect permet à n'importe quel site web d'apporter quelques-unes des puissantes fonctionnalités de Facebook à ses propres utilisateurs. Les sites web qui en bénéficient se reconnaissent déjà à la présence d'un gros bouton bleu "Connect with Facebook". Parmi les plus connus : digg.com, cnet.com, netvibes.com, yelp.com, etc. La section suivante donne la liste des quatre principales raisons d'implémenter Facebook Connect sur un site existant. Système d'Authentification en un Clic Tout comme OpenID, Facebook Connect permet aux sites web de proposer à ses utilisateurs une connexion automatique à partir de leur session Facebook. Une fois la "connexion" entre le site web et Facebook approuvée par l'utilisateur, la session Facebook est automatiquement transmise au site, permettant d'épargner à l'utilisateur une énième procédure d'enregistrement et de mot de passe à mémoriser. Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Search Obtenir plus d'Informations sur l'Utilisateur Une autre fonctionnalité clef de Facebook Connect est la quantité d'informations apportées. Alors qu'un utilisateur ne délivrera habituellement qu'un minimum d'informations le concernant sur un nouveau site, Facebook Connect permet quant à lui d'obtenir facilement des informations additionnelles telles que l'âge, le sexe, la localisation, la photo de profil, etc. enrichissant d'autant plus le site. Les conditions d'utilisation de Facebook Connect rappellent clairement que la conservation des informations personnelles est interdite sans l'accord explicite de l'utilisateur. Mais l'information disponible peut être utilisée pour pré-remplir des formulaires et demander confirmation en un clic. Le site web peut aussi se contenter des informations publiques telles que le prénom et le nom sans avoir besoin de les enregistrer. Communication Virale grâce au Flux d'Actualités La possibilité d'interagir avec le flux d'actualités de l'utilisateur, d'inviter des amis ou de bien de publier sur le mur d'un ami permet au site web d'utiliser pleinement le potentiel viral de Facebook pour communiquer. En effet, n'importe quel site avec une composante communautaire peut ainsi véritablement bénéficier de cette fonctionnalité, tant que l'information publiée sur Facebook a un intérêt social qui peut intéresser des amis et des amis d'amis. Tirer Parti du Graphe Social Existant Pour un site web dont le service dépend d'un graphe social (un réseau d'amis ou de connaissances), le coût pour démarrer une première communauté, avec suffisamment de liens entre les utilisateurs pour leur permettre d'interagir et de bénéficier du service, est colossal. En donnant un accès facile à la liste des amis Facebook d'un utilisateur, Facebook Connect réduit considérablement ce coût, en évitant à l'utilisateur de devoir chercher ses "amis déjà enregistrés". Configurer un Premier Projet avec le Plugin sfFacebookConnectPlugin http://www.symfony-project.org/more-with-symfony/1_4/fr/12-Developing-for-Facebook[31/12/2010 02:35:04] powered by google The More with symfony book | Développer pour Facebook | symfony | Web PHP Framework Créer l'Application sur Facebook Pour commencer, un compte Facebook est nécessaire, avec l'application "Developer" installée. Pour créer l'application, la seule information nécessaire dans un premier temps est le nom de l'application. Installer et Configurer sfFacebookConnectPlugin La prochaine étape est de lier les utilisateurs Facebook avec les utilisateurs sfGuard. C'est la principale fonction du sfFacebookConnectPlugin, que j'ai créé et auquel d'autres développeurs symfony ont rapidement contribué. Une fois ce plugin installé, il y a une étape de configuration simple mais nécessaire. Les paramètres API key, application secret, et application ID doivent être précisés dans le fichier app.yml de l'application symfony. # default values all: facebook: api_key: xxx api_secret: xxx api_id: xxx redirect_after_connect: false redirect_after_connect_url: '' connect_signin_url: 'sfFacebookConnectAuth/signin' app_url: '/my-app' guard_adapter: ~ js_framework: none # none, jQuery or prototype. sf_guard_plugin: profile_class: sfGuardUserProfile profile_field_name: user_id profile_facebook_uid_name: facebook_uid # WARNING this column must be of type varchar! 100000398093902 is a valid uid for example! profile_email_name: email profile_email_hash_name: email_hash facebook_connect: load_routing: true user_permissions: [] Avec les versions de symfony antérieures à 1.2, l'option load_routing doit être définie à la valeur false, car elle utilise le nouveau système de routing sfRouting. Configurer une Application Facebook Si le projet est une application Facebook et pas un site Facebook Connect, le seul autre paramètre important est app_url qui précise l'adresse relative de l'application dans Facebook. Par exemple pour l'application http://apps.facebook.com/my-app la valeur du paramètre app_url sera /my-app. Configurer un Site Facebook Connect Si le projet est un site Facebook Connect, les valeurs par défaut des autres paramètres pourront être conservées la plupart du temps : redirect_after_connect permet de modifier le comportement du plugin après un clic sur le bouton "Connect with Facebook". Par défaut le plugin reproduit le comportement de sfGuardPlugin après la création d'un nouveau compte. js_framework permet de préciser l'utilisation d'un framework JavaScript. Il est fortement recommandé d'en utiliser un, tel que jQuery par exemple, sur les sites Facebook Connect. En effet, l'API JavaScript de Facebook est relativement lourde et peut entraîner des erreurs fatales (!) sur IE6 si le chargement du fichier a lieu en cours de rendu du DOM. user_permissions est le tableau des permissions qui seront affectées à un nouvel utilisateur Facebook. Connecter sfGuard avec Facebook Le lien entre un utilisateur Facebook et le système d'authentification de sfGuardPlugin est réalisé assez logiquement en utilisant une colonne facebook_uid dans la table Profile. Le plugin part du principe que le lien entre l'objet sfGuardUser et son profil est obtenu en http://www.symfony-project.org/more-with-symfony/1_4/fr/12-Developing-for-Facebook[31/12/2010 02:35:04] The More with symfony book | Développer pour Facebook | symfony | Web PHP Framework utilisant la méthode getProfile(). C'est le comportement par défaut avec sfPropelGuardPlugin mais doit être configuré spécifiquement avec sfDoctrineGuardPlugin. Voici un schema.yml type : Pour Propel : sf_guard_user_profile: _attributes: { phpName: UserProfile } id: user_id: { type: integer, foreignTable: sf_guard_user, foreignReference: id, onDelete: cascade } first_name: { type: varchar, size: 30 } last_name: { type: varchar, size: 30 } facebook_uid: { type: varchar, size: 20 } email: { type: varchar, size: 255 } email_hash: { type: varchar, size: 255 } _uniques: facebook_uid_index: [facebook_uid] email_index: [email] email_hash_index: [email_hash] Pour Doctrine : sfGuardUserProfile: tableName: sf_guard_user_profile columns: user_id: { type: integer(4), notnull: true } first_name: { type: string(30) } last_name: { type: string(30) } facebook_uid: { type: string(20) } email: { type: string(255) } email_hash: { type: string(255) } indexes: facebook_uid_index: fields: [facebook_uid] unique: true email_index: fields: [email] unique: true email_hash_index: fields: [email_hash] unique: true relations: sfGuardUser: type: one foreignType: one class: sfGuardUser local: user_id foreign: id onDelete: cascade foreignAlias: Profile Si le projet utilise Doctrine mais que la propriété foreignAlias n'est pas définie à Profile, alors le plugin ne fonctionnera pas. Mais une simple méthode getProfile()dans la classe sfGuardUser qui pointe vers la table Profile suffit à contourner le problème ! Il faut bien faire attention au type de la colonne facebook_uid qui doit être un varchar car les nouveaux profils Facebook ont des uids supérieurs à 10^15. Il convient de rester prudent en utilisant une colonne varchar indexée plutôt que de vouloir tenter l'utilisation d'une colonne bigint dont le comportement n'est pas toujours le même selon le SGBD ou l'ORM utilisé. Les deux autres colonnes sont moins importantes : email et email_hash ne servent que dans le cas où Facebook Connect est utilisé sur un site web qui possède déjà des utilisateurs. Dans ce cas Facebook propose un traitement assez complexe pour tenter d'associer les comptes existants avec les utilisateurs qui essaieraient ensuite Facebook Connect en utilisant un hash de l'email du compte existant. Bien sûr, le processus est simplifié par une tâche fournie dans le plugin sfFacebookConnectPlugin décrite plus tard dans ce chapitre. Choisir entre FBML et XFBML : Problème Résolu par symfony http://www.symfony-project.org/more-with-symfony/1_4/fr/12-Developing-for-Facebook[31/12/2010 02:35:04] The More with symfony book | Développer pour Facebook | symfony | Web PHP Framework Maintenant que tout est correctement configuré, le développement de l'application peut commencer. Facebook propose plusieurs balises spécifiques qui permettent d'afficher de véritables fonctionnalités, comme par exemple un formulaire d'invitation d'amis ou un système entier de commentaires. Ces balises sont des tags FBML ou XFBML. Les balises FBML et XFBML sont assez similaires, mais le choix entre l'un ou l'autre des formats dépend en fait de l'affichage ou non de l'application par Facebook. Si le projet est un site Facebook Connect, alors il n'y a qu'un choix possible : XFBML. Si c'est une application Facebook, il y a deux choix : Inclure l'application dans Facebook au travers d'une IFrame et utiliser du XFBML dans cette IFrame ; Laisser Facebook se charger du rendu directement dans leur page, et utiliser le FBML. Facebook encourage les développeurs à utiliser leur "inclusion transparente" ou en d'autres termes le FBML. En effet elle dispose de certains avantages : Pas d'IFrame, qui restent toujours difficiles à gérer, car il faut en permanence se souvenir si le lien concerne l'IFrame ou bien la fenêtre entière ; Les balises FBML sont interprétées directement par le serveur Facebook et permettent ainsi d'afficher des informations privées concernant l'utilisateur sans avoir à communiquer au préalable avec le serveur Facebook. Cela constitue donc un aller / retour en moins entre les deux serveurs ; Pas besoin de transmettre les informations de session Facebook de page en page. Mais le FBML a des inconvénients certains : Tout le JavaScript est automatiquement inclus dans une sandbox, rendant impossible toute utilisation de librairie extérieure, comme une Google Maps, jQuery ou tout autre système de statistiques tels que Google Analytics qui est supporté officiellement par Facebook ; Le FBML est censé être plus rapide car certains appels au serveur sont économisés. Cependant si l'application n'est pas particulièrement lourde, l'héberger sur son propre serveur sera beaucoup plus rapide ; C'est plus difficile à déboguer, particulièrement pour les erreurs 500 qui sont attrapées par Facebook et remplacées par une erreur standard. Donc quel est le choix recommandé ? La bonne nouvelle c'est qu'avec symfony et le plugin sfFacebookConnectPlugin, il n'y a pas de choix à faire ! Il est possible d'écrire des applications agnostiques et passer indifféremment de l'IFrame à l'inclusion directe dans Facebook ou au site externe Facebook Connect avec le même code. Ceci est possible car, techniquement, la principale différence entre ces trois environnements réside dans le layout... qui est très facile à interchanger dans symfony. Voici deux exemples de layout : Le layout d'une application FBML : <?php sfConfig::set('sf_web_debug', false); ?> <fb:title><?php echo sfContext::getInstance()->getResponse()->getTitle() ?></fb:title> <?php echo $sf_content ?> Le layout d'une application XFBML ou d'un site Facebook Connect : <?php use_helper('sfFacebookConnect')?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml"> <head> <?php include_http_metas() ?> <?php include_metas() ?> <?php include_title() ?> <script type="text/javascript" src="/sfFacebookConnectPlugin/js/animation/animation.js"></script> </head> <body> <?php echo $sf_content ?> <?php echo include_facebook_connect_script() ?> </body> </html> Pour passer de l'un à l'autre automatiquement, il suffit d'ajouter le code suivant dans le fichier http://www.symfony-project.org/more-with-symfony/1_4/fr/12-Developing-for-Facebook[31/12/2010 02:35:04] The More with symfony book | Développer pour Facebook | symfony | Web PHP Framework actions.class.php : public function preExecute() { if (sfFacebook::isInsideFacebook()) { $this->setLayout('layout_fbml'); } else { $this->setLayout('layout_connect'); } } Il y a une petite différence entre une balise FBML et XFBML qui ne se trouve pas dans le layout : les balises FBML peuvent être fermées, contrairement aux balises XFBML. Il suffit donc de toujours remplacer les tags de la forme : [html] <fb:profile-pic uid="12345" size="normal" width="400" /> par : [html] <fb:profile-pic uid="12345" size="normal" width="400"></fb:profile-pic> Bien sûr, il faut aussi configurer l'onglet Facebook Connect de l'application dans les paramètres développeur de l'application de Facebook, même si le but final n'est que de faire du FBML. Cependant, l'énorme avantage de faire ceci est de pouvoir tester l'application localement. Si l'application Facebook utilise des balises FBML, ce qui est quasiment inévitable, la seule façon de visualiser le résultat final consisterait logiquement à mettre le code en ligne afin de le tester directement dans Facebook ! Heureusement grâce à Facebook Connect, les balises XFBML peuvent être rendues en dehors du domaine facebook.com. Et, comme les portions de code précédentes l'ont montré, la seule différence entre XFBML et FBML concerne le layout. Cette solution permet donc de visualiser les balises FBML en local, à condition bien sûr d'être connecté à Internet. De plus, avec un environnement de développement visible sur Internet, tel qu'un serveur web ou un simple ordinateur avec un port 80 ouvert par exemple, même les parties de l'application qui dépendent du système d'authentification de Facebook fonctionneront en dehors du domaine facebook.com. C'est en effet grâce à Facebook Connect une fois de plus. L'application peut donc être entièrement testée avant d'être mise en ligne sur Facebook. L'Application de Démonstration "Hello You" Avec le code suivant dans la vue du module home, l'application "Hello You" est terminée : <?php $sfGuardUser = sfFacebook::getSfGuardUserByFacebookSession(); ?> Hello <fb:name uid="<?php echo $sfGuardUser ? $sfGuardUser->getProfile()>getFacebookUid() : '' ?>"></fb:name> sfFacebookConnectPlugin convertit automatiquement le visiteur Facebook en un utilisateur sfGuard. Cela permet une intégration très facile avec du code symfony existant qui repose sur sfGuardPlugin. Facebook Connect Comment Facebook Connect fonctionne et Différentes Stratégies d'Intégration Pour faire simple, Facebook Connect partage sa session avec celle du site. Cette opération est réalisée au travers de la copie du cookie d'authentification de Facebook vers le site web en ouvrant successivement une IFrame dans le site qui pointe vers une page Facebook. Cette même page Facebook ouvre elle aussi une IFrame qui pointe vers le site. Pour ce faire, Facebook Connect a besoin d'avoir accès au site ce qui empêche d'utiliser ou de tester l'authentification Facebook Connect en local. Le point d'entrée sur le site est le fichier xd_receiver.htm que sfFacebookConnectPlugin installe automatiquement. Bien sûr il ne faut pas oublier d'utiliser la commande symfony plugin:publish-assets pour rendre le fichier accessible publiquement. Lorsque cette opération est terminée, la librairie officielle de Facebook permet d'utiliser la http://www.symfony-project.org/more-with-symfony/1_4/fr/12-Developing-for-Facebook[31/12/2010 02:35:04] The More with symfony book | Développer pour Facebook | symfony | Web PHP Framework session Facebook. Le plugin sfFacebookConnectPlugin crée en plus un utilisateur sfGuard lié à cette session Facebook, qui s'intègre donc sans souci avec le site web existant. C'est pour cette raison que le plugin redirige automatiquement l'utilisateur vers l'action sfFacebookConnectAuth/signIn par défaut, une fois le bouton Facebook Connect cliqué et la connexion validée. Le plugin cherche d'abord si un utilisateur existe avec le même UID Facebook ou bien le même hash d'email (voir la section "Connecter les Utilisateurs Existants avec leur Compte Facebook" à la fin de ce chapitre). Si aucun n'est trouvé, un utilisateur vierge est alors créé. Une autre stratégie d'intégration classique consiste à ne pas créer l'utilisateur directement, mais de le rediriger d'abord vers un formulaire d'enregistrement spécifique. De là, il est éventuellement possible d'utiliser la session Facebook afin de pré-remplir des informations, par exemple, en ajoutant la fonction suivante dans le formulaire d'enregistrement : public function setDefaultsFromFacebookSession() { if ($fb_uid = sfFacebook::getAnyFacebookUid()) { $ret = sfFacebook::getFacebookApi()->users_getInfo( array( $fb_uid ), array( 'first_name', 'last_name', ) ); if ($ret && count($ret)>0) { if (array_key_exists('first_name', $ret[0])) { $this->setDefault('first_name',$ret[0]['first_name']); } if (array_key_exists('last_name', $ret[0])) { $this->setDefault('last_name',$ret[0]['last_name']); } } } Pour utiliser cette deuxième stratégie, souvent recommandée, il suffit de spécifier deux paramètres dans le fichier app.yml. Le premier paramètre, redirect_after_connect indique qu'il faille rediriger après la connexion Facebook Connect tandis que le second, redirect_after_connect_url précise la route à utiliser pour réaliser cette redirection. # default values all: facebook: redirect_after_connect: true redirect_after_connect_url: '@register_with_facebook' Le Filtre Facebook Connect Une chose importante à savoir, c'est que les utilisateurs de Facebook sont très souvent connectés à Facebook lorsqu'ils sont sur Internet, c'est un avantage indéniable. Par conséquent, le filtre sfFacebookConnectRememberMeFilter peut se révéler très utile. Si un utilisateur utilise le site web alors qu'il est déjà connecté à Facebook, le filtre sfFacebookConnectRememberMeFilter va automatiquement le connecter sur le site comme le ferait le filtre classique "Remember me" de symfony. $sfGuardUser = sfFacebook::getSfGuardUserByFacebookSession(); if ($sfGuardUser) { $this->getContext()->getUser()->signIn($sfGuardUser, true); } Malheureusement, il subsiste un inconvénient majeur à ce filtre : les utilisateurs ne peuvent plus se déconnecter du site, car tant qu'ils sont connectés sur Facebook, ils seront automatiquement http://www.symfony-project.org/more-with-symfony/1_4/fr/12-Developing-for-Facebook[31/12/2010 02:35:04] The More with symfony book | Développer pour Facebook | symfony | Web PHP Framework reconnectés sur le site. Cette fonctionnalité est donc à manier avec parcimonie et précaution. Implémentation Propre afin d'Eviter l'Erreur Fatale d'IE Un des pires bugs que l'on puisse rencontrer sur un site Internet est l'erreur "Operation aborted" sur IE, qui fait tout simplement planter le rendu du site... côté client ! C'est en effet dû à la mauvaise qualité du moteur de rendu d'IE6 et d'IE7 qui peuvent tous deux planter si des éléments DOM sont ajoutés à l'élément body depuis un script qui n'est pas directement fils de l'élément body. Malheureusement, c'est souvent le cas si du JavaScript Facebook est appelé sans faire attention. Il faut donc prendre garde et bien l'insérer directement dans le body à la fin du document. C'est d'autant plus facile à respecter avec symfony grâce notamment aux slots. Un slot dédié au script Facebook Connect est utilisé dans la template si nécessaire et est inclus à la fin du layout, juste avant la balise de fermeture </body>. // in a template that uses a XFBML tag or a Facebook Connect button slot('fb_connect'); include_facebook_connect_script(); end_slot(); // just before </body> in the layout to avoid problems in IE if (has_slot('fb_connect')) { include_slot('fb_connect'); } Bonnes Pratiques pour les Applications Facebook Grâce au plugin sfFacebookConnectPlugin, l'intégration avec le plugin sfGuardPlugin est simplifiée et le choix entre FBML, IFrame ou un site Facebook Connect peut attendre la dernière minute. Afin d'aller plus loin et de créer une véritable application utilisant plus de fonctionnalités Facebook, il convient de donner quelques pratiques importantes qui profitent de toute la puissance de symfony. Configurer Plusieurs Serveurs Facebook Connect de Test Un point important de la philosophie symfony concerne le débogage rapide et efficace de l'application. Développer sur Facebook peut rendre la tâche particulièrement difficile, car beaucoup de fonctionnalités nécessitent une connexion Internet (pour communiquer avec le serveur Facebook), ainsi qu'un port 80 ouvert pour échanger les cookies d'authentification. De plus, il existe une contrainte supplémentaire : une application Facebook Connect ne peut être reliée qu'à un seul hôte. C'est un véritable problème si l'application est développée sur une machine, puis testée sur une autre, mise en pré-production sur une troisième et utilisée finalement sur une quatrième. Dans ce cas la solution la plus simple consiste à créer une application par serveur dans les paramètres développeur de Facebook, et de créer un environnement symfony pour chacune d'elles. C'est trivial à réaliser dans symfony puisqu'il suffit d'un simple copier-coller du fichier frontend_dev.php en son équivalent frontend_preprod.php. Il ne reste alors qu'à éditer le nouveau fichier en remplaçant l'environnement dev par un nouvel environnement preprod. $configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'preprod', true); L'étape suivante consiste à modifier le fichier app.yml afin de configurer les différentes applications Facebook correspondantes aux différents environnements. prod: facebook: api_key: xxx api_secret: xxx api_id: xxx dev: facebook: api_key: xxx api_secret: xxx api_id: xxx http://www.symfony-project.org/more-with-symfony/1_4/fr/12-Developing-for-Facebook[31/12/2010 02:35:04] The More with symfony book | Développer pour Facebook | symfony | Web PHP Framework preprod: facebook: api_key: xxx api_secret: xxx api_id: xxx Désormais, chaque application est testable sur chaque serveur distinct en utilisant le point d'entrée frontend_xxx.php correspondant. Utiliser le Système de Log de symfony pour Déboguer le FBML Interchanger facilement le layout permet à la fois de tester et de développer une application FBML quasiment entièrement en dehors de Facebook. Cependant, le test final dans Facebook peut malgré tout résulter en un message d'erreur particulièrement obscur. En effet, le principal problème lorsqu'il s'agit de visualiser le FBML directement dans Facebook vient du fait que les erreurs 500 sont remplacées par un message d'erreur complètement inutile. De plus, la web debug toolbar, à laquelle les développeurs symfony sont rapidement accrocs, ne peut être utilisée correctement dans une application FBML. Heureusement l'excellent système de log de symfony est là pour nous sauver. Le plugin sfFacebookConnectPlugin loggue déjà automatiquement les actions les plus importantes et il est facile de rajouter des lignes dans le fichier partout dans l'application. if (sfConfig::get('sf_logging_enabled')) { sfContext::getInstance()->getLogger()->info($message); } Eviter les Mauvaises Redirections Facebook avec un Proxy Un bug étrange de Facebook est qu'une fois Facebook Connect configuré pour une application, le serveur hébergeant l'application est considéré comme page d'accueil par défaut de l'application. Bien qu'il soit possible de préciser une page d'accueil, elle doit figurer dans le domaine du serveur hébergeur. Cela peut paraître gênant si c'est une application Facebook dont la page d'accueil se trouve dans le domaine apps.facebook.com ! Aucune autre solution n'existe que de se rendre et configurer la page d'accueil vers une action symfony très simple qui redirige à un endroit désiré. Le code suivant redirige vers la page d'accueil de Facebook : public function executeRedirect(sfWebRequest $request) { return $this>redirect('http://apps.facebook.com'.sfConfig::get('app_facebook_app_url')); } Utiliser le Helper fb_url_for() dans les Applications Facebook Pour garder une application agnostique et utilisable jusqu'à la dernière minute autant en FBML dans Facebook qu'en XFBML dans une IFrame, un problème important persiste : le routage. Pour une application FBML, les liens dans l'application doivent pointer vers /appname/symfony-route ; pour une application IFrame, il est important de passer l'information de session Facebook d'une page à une autre. Le plugin sfFacebookConnectPlugin fournit pour cela un helper dédié qui permet de faire exactement les deux automatiquement : fb_url_for(). Rediriger dans une Application Facebook Les développeurs symfony s'habituent rapidement à rediriger après une requête post réussie. C'est en effet une bonne pratique de développement web qui empêche entre autre le double post. Rediriger dans une application FBML, cependant, ne fonctionne pas comme attendu. A la place, une balise FBML spécifique est nécessaire afin d'informer Facebook de faire la redirection. Toujours dans un ultime but précis de rester agnostique, une méthode statique spéciale, redirect(), existe dans la classe sfFacebook, qui peut être utilisée par exemple dans l'action de sauvegarde d'un formulaire. if ($form->isValid()) { http://www.symfony-project.org/more-with-symfony/1_4/fr/12-Developing-for-Facebook[31/12/2010 02:35:04] The More with symfony book | Développer pour Facebook | symfony | Web PHP Framework $form->save(); return sfFacebook::redirect($url); } Connecter des Utilisateurs Existants avec leur Compte Facebook L'un des principaux buts de Facebook Connect consiste à faciliter le processus d'enregistrement pour les nouveaux utilisateurs. Cependant, une autre utilisation intéressante concerne aussi la connexion des utilisateurs existants à partir de leur compte Facebook. Cette fonctionnalité est utile soit pour obtenir plus d'informations sur eux, ou bien communiquer dans leur feed mais aussi pour leur proposer une authentification en un seul clic. Cette tâche est réalisable de deux manières différentes. Inciter les utilisateurs sfGuard existants à cliquer sur le bouton "Connect with Facebook". L'action sfFacebookConnectAuth/signIn ne créera pas un nouvel utilisateur sfGuard si elle détecte un utilisateur déjà authentifié, mais reliera en revanche cet utilisateur avec l'UID de la session Facebook reconnue. Utiliser le système de reconnaissance d'email de Facebook. Lorsqu'un nouvel utilisateur utilise Facebook Connect sur un site, Facebook est capable de fournir un hash spécifique de ses emails, qui peuvent ensuite être comparés aux hashes des emails existants en base. L'objectif est ainsi de reconnaître un utilisateur existant. Mais, vraisemblablement, pour des raisons de sécurité, il est impossible d'obtenir les hashes des emails d'un utilisateur si ces derniers n'ont pas été soumis préalablement à l'API de Facebook. C'est pourquoi il est utile d'enregistrer les hashes d'email de tous les nouveaux utilisateurs régulièrement, afin de les reconnaître ultérieurement. C'est exactement le besoin que remplit la tâche registerUsers, qui a été migrée sur symfony 1.2 par Damien Alexandre. Dans l'idéal, cette tâche devrait être exécutée au moins toutes les nuits afin d'enregistrer les nouvelles créations de comptes, ou bien, juste après la création d'un nouveau compte avec la méthode registerUsers de sfFacebookConnect. [php] sfFacebookConnect::registerUsers(array($sfGuardUser)); Aller Plus Loin J'espère que cet article a rempli son objectif ; aider et inciter les développeurs à démarrer le développement d'une application Facebook sous symfony, et expliquer comment bénéficier de toute la puissance de symfony tout au long de ce développement. Néanmoins, le plugin sfFacebookConnectPlugin ne remplace pas l'API originale de Facebook, et pour apprendre à utiliser toutes les fonctionnalités du développement sur la plate-forme Facebook, il faudra visiter son site. Pour conclure, je tiens à remercier toute la communauté symfony pour sa qualité et sa générosité, et particulièrement tous ceux qui ont déjà contribué au plugin sfFacebookConnectPlugin au travers de leurs commentaires et patches : Damien Alexandre, Thomas Parisot, Maxime Picaud, Alban Creton et désolé pour ceux que j'aurais pu oublier. Bien sûr si vous pensez qu'il manque quelque chose dans le plugin, il est toujours possible de contribuer aussi ! « Windows et symfony Tirer Profit de la Ligne de Commande » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Open-Source Products http://www.symfony-project.org/more-with-symfony/1_4/fr/12-Developing-for-Facebook[31/12/2010 02:35:04] Services The More with symfony book | Développer pour Facebook | symfony | Web PHP Framework Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/12-Developing-for-Facebook[31/12/2010 02:35:04] The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Tirer Profit de la Ligne de Commande You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Geoffrey Bachelet Symfony 1.1 a introduit un système d'exécution de tâches en ligne de commande moderne, puissant et flexible en remplacement de l'ancien système basé sur pake. De version en version, le système de tâches de symfony s'est enrichi afin d'être ce qu'il est aujourd'hui. De nombreux développeurs ne perçoivent pas toute la valeur ajoutée des tâches. Bien souvent, ces développeurs ne réalisent pas aussi la puissance de la ligne de commande. Ce chapitre plonge le lecteur dans l'univers des tâches automatiques, de leur usage le plus simple au plus avancé, en démontrant à la fois combien cet outil aide le développeur au quotidien, et de quelle manière en tirer le mieux profit. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Introduction Une tâche est une partie du code qui s'exécute depuis une interface en ligne de commande en utilisant le script php symfony présent à la racine du projet. N'importe quel développeur symfony a déjà utilisé les tâches de symfony en exécutant par exemple la si connue tâche cache:clear dans un shell. Cette tâche est aussi bien connue pour sa forme raccourcie cc. $ php symfony cc Symfony fournit nativement un large choix de jeux de tâches automatiques pour une grande variété d'usages. Il est possible d'obtenir une liste complète de toutes les tâches disponibles en exécutant le script symfony sans lui fournir la moindre option ou argument. $ php symfony La sortie générée dans la console ressemblera à celle ci-après. Le contenu a été tronqué : Usage: symfony [options] task_name [arguments] Options: --help --quiet --trace --version --color --xml -H -q -t -V Display this help message. Do not log messages to standard output. Turn on invoke/execute tracing, enable full backtrace. Display the program version. Forces ANSI color output. To output help as XML Available tasks: :help :list app :routes cache :clear Displays help for a task (h) Lists tasks Displays current routes for an application Clears the cache (cc, clear-cache) Le lecteur aura certainement remarqué que les tâches sont groupées. Les groupes de tâches sont appelés des espaces de nom. Les noms des tâches sont généralement composés d'un espace de nom et d'un nom, séparés par un caractère ":". Certaines commandes spéciales telles que help et list dérogent à la règle car elles sont exemptes d'espace de nom. Ce schéma de nommage permet de catégoriser facilement les tâches les unes par rapport aux autres, et il s'agira plus tard de choisir un espace de nom pertinent pour chaque nouvelle tâche développée. Ecrire ses Propres Tâches Débuter avec l'écriture de tâches symfony n'est qu'une question de minutes. En effet, les seules http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] Chapter Content Introduction Ecrire ses Propres Tâches Le Système d'Options Les Options Les Arguments Les Arguments et Options par Défaut Options Spéciales Accéder à la Base de Données Envoyer des Emails Déléguer la Génération du Contenu Utiliser le Plugin de Décoration de Swift Mailer Utiliser une Librairie Externe de Template Obtenir le Meilleur des Deux Mondes Générer des URLs Accéder au Système The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework choses à faire sont de créer une classe de tâche, puis de nommer la tâche avec un nom et un espace de nom, et enfin de lui ajouter un peu de logique. C'est tout ce dont il est nécessaire afin d'exécuter une première commande personnalisée. Le code ci-dessous déclare un exemple de tâche simple Hello World! dans un fichier lib/task/sayHelloTask.class.php. // lib/task/sayHelloTask.class.php class sayHelloTask extends sfBaseTask { public function configure() { $this->namespace = 'say'; $this->name = 'hello'; } public function execute($arguments = array(), $options = array()) { echo 'Hello, World!'; } } Il ne reste alors plus qu'à exécuter cette nouvelle tâche à l'aide de la commande suivante. $ php symfony say:hello Le seul but de cette tâche est d'imprimer la chaîne Hello, World! dans la console. C'est déjà un bon point de départ ! Bien sûr, les tâches ne servent pas seulement à afficher du contenu dans la console directement avec les fonctions echo ou print. Etendre la classe abstraite sfBaseTask permet ainsi au développeur de bénéficier d'autres méthodes pratiques, telles que log() qui remplit exactement le même besoin, à savoir afficher du contenu. d'Internationalisation - I18N Remanier les Tâches Exécuter une Tâche dans une Autre Manipuler le Système de Fichiers Utiliser des Squelettes pour Générer des Fichiers Utiliser une Option Dry-Run Ecrire des Tests Unitaires Methodes Helper : Logging Méthodes Helper : Interaction avec l'Utilisateur Bonus : Utiliser les Tâches avec une Crontab Bonus : Utiliser STDIN Conclusion Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) public function execute($arguments = array(), $options = array()) { $this->log('Hello, World!'); } Un même appel à une tâche peut conduire à différents contenus en sortie, c'est pourquoi il convient généralement d'utiliser la méthode logSection(). public function execute($arguments = array(), $options = array()) { $this->logSection('say', 'Hello, World!'); } A ce stade du chapitre, le lecteur aura probablement remarqué la présence des deux arguments transmis à la méthode execute(), $arguments et $options. Ces deux variables servent à contenir tous les arguments et options passés à la tâche à l'exécution. Les notions d'arguments et d'options seront décrites en détail plus loin. Pour l'instant, il s'agit d'ajouter un peu plus d'interactivité à la tâche en permettant à l'utilisateur de spécifier à qui il souhaite dire bonjour. public function configure() { $this->addArgument('who', sfCommandArgument::OPTIONAL, 'Who to say hello to?', 'World'); } public function execute($arguments = array(), $options = array()) { $this->logSection('say', 'Hello, '.$arguments['who'].'!'); } La commande suivante : $ php symfony say:hello Geoffrey Devrait ainsi produire le résultat ci-dessous. >> say Hello, Geoffrey! C'est facile n'est-ce pas ? Par la même occasion, il convient d'ajouter quelques métadonnées supplémentaires à la commande décrivant par exemple la fonction qu'elle remplit. Pour ce faire, il suffit de définir une valeur pour les deux propriétés briedDescription et description. http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] and more... Search powered by google The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework public function configure() { $this->namespace = 'say'; $this->name = 'hello'; $this->briefDescription = 'Simple hello world'; $this->detailedDescription = <<<EOF The [say:hello|INFO] task is an implementation of the classical Hello World example using symfony's task system. [./symfony say:hello|INFO] Use this task to greet yourself, or somebody else using the [--who|COMMENT] argument. EOF; $this->addArgument('who', sfCommandArgument::OPTIONAL, 'Who to say hello to?', 'World'); } Comme le montre cet exemple, il est possible d'utiliser un jeu basique de balises pour décorer la description. La commande symfony help permet de contrôler le rendu des métadonnées de la tâche: $ php symfony help say:hello Le Système d'Options Dans les tâches symfony, les options sont organisées en deux ensembles distincts : les options et les arguments. Les Options Les options sont les données transmises à l'aide de traits d'union. Elles peuvent être ajoutées à la ligne de commande dans un ordre totalement arbitraire. Les options acceptent ou non une valeur selon qu'elles agissent comme des valeurs booléennes ou non. Très souvent, les options possèdent une forme courte et longue. La forme la plus longue est généralement invoquée en spécifiant deux traits d'union tandis que sa forme courte en requiert seulement un. Il existe aussi des options récurrentes dans le système de tâches de symfony. C'est le cas par exemple des options d'aide (--help ou -h), de verbosité (--quiet ou -q) ou bien de version (-version ou -V). Les options sont définies à l'aide de la classe sfCommandOption et stockées dans une classe sfCommandOptionSet. Les Arguments Les arguments constituent une suite de données ajoutées à la ligne de commande. Ils doivent obligatoirement être spécifiés dans l'ordre dans lequel ils ont été définis, et doivent être entourés de guillemets dans le cas où leur valeur contient un espace (les espaces peuvent être échappés). Il existe deux types d'arguments : les obligatoires et les facultatifs. Tous les arguments déclarés comme étant facultatifs doivent accueillir une valeur par défaut. Les arguments sont évidemment définis à l'aide d'une classe sfCommandArgument et stockés dans un objet sfCommandArgumentSet. Les Arguments et Options par Défaut Chaque tâche symfony accueille un jeu d'options et d'arguments par défaut : --help (-H) affiche un message d'aide ; --quiet (-q) n'affiche aucun message sur la sortie standard ; --trace (-t) active la trace d'exécution en incluant la pile d'exceptions complète ; --version (-V) affiche la version du programme ; http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework --color force une sortie avec les couleurs ANSI. Options Spéciales Le système de tâches de symfony est capable de reconnaître deux options très spéciales, application et env. L'option application est nécessaire lorsqu'il s'agit d'accéder à une instance de la classe sfApplicationConfiguration plutôt qu'une instance de sfProjectConfiguration. C'est le cas, par exemple, lorsque le développeur souhaite générer des URLs depuis le système de routage. Or, ce dernier est généralement associé à une instance d'une application spécifique. Lorsqu'une option application est passée à la tâche, symfony la détecte automatiquement et crée l'objet sfApplicationConfiguration correspondant au lieu de l'objet sfProjectConfiguration par défaut. Il est intéressant de noter qu'il est possible de définir un jeu de valeurs par défaut pour cette option, ce qui permet ainsi de s'éviter de passer une application à la main à chaque exécution de la tâche. L'option env contrôle bien évidemment l'environnement dans lequel la tâche est exécutée. Si aucun environnement n'est passé, c'est l'environnement de test qui est sélectionné par défaut. De la même manière qu'avec l'option application, il est possible de définir une valeur par défaut pour l'option env qui sera ensuite utilisée par symfony. Comme les options application et env ne sont pas incluses par défaut dans le jeu d'options, elles doivent être ajoutées manuellement dans la classe de la tâche. public function configure() { $this->addOptions(array( new sfCommandOption('application', null, sfCommandOption::PARAMETER_REQUIRED, 'The application name', 'frontend'), new sfCommandOption('env', null, sfCommandOption::PARAMETER_REQUIRED, 'The environment', 'dev'), )); } Dans cet exemple, l'application frontend sera utilisée automatiquement, et à moins qu'un autre environnement ne soit spécifié, la tâche s'exécutera dans le contexte de l'environnement dev. Accéder à la Base de Données Avoir accès à la base de données depuis l'intérieur d'une tâche symfony implique de disposer d'une instance de la classe sfDatabaseManager. public function execute($arguments = array(), $options = array()) { $databaseManager = new sfDatabaseManager($this->configuration); } L'objet de connexion de l'ORM peut également être accédé directement comme le montre l'exemple de code ci-dessous. public function execute($arguments = array(), $options = array()) { $databaseManager = new sfDatabaseManager($this->configuration); $connection = $databaseManager->getDatabase()->getConnection(); } Mais qu'en est-il des projets pour lesquels plusieurs connexions sont définies dans le fichier databases.yml ? Il convient par exemple d'ajouter une option connection à la tâche pour satisfaire ce besoin. public function configure() { $this->addOption('connection', sfCommandOption::PARAMETER_REQUIRED, 'The connection name', 'doctrine'); } public function execute($arguments = array(), $options = array()) { $databaseManager = new sfDatabaseManager($this->configuration); $connection = $databaseManager->getDatabase(isset($options['connection']) ? $options['connection'] : null)->getConnection(); } http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework Comme d'habitude, une valeur par défaut peut être définie pour cette option. Tous les objets de modèle de la base de données sont à présent manipulables comme s'ils se trouvaient dans un contexte d'application symfony traditionnelle. Attention lorsqu'il s'agit de manipuler des objets de modèle d'ORM en masse dans les tâches. En effet, les deux ORMs Propel et Doctrine souffrent d'un bug très connu de PHP relatif aux références cycliques et au ramasse miettes (garbage collector). Ce bug provoque une fuite de mémoire et affecte toutes les versions strictement inférieures à la 5.3. Envoyer des Emails L'un des usages les plus répandus des tâches concerne l'envoi d'emails. En effet, cette tâche n'était pas si aisée avant symfony 1.3. Heureusement, les choses ont changé et symfony s'accompagne à présent d'une intégration complète de la librairie open-source PHP Swift Mailer. Pourquoi ne pas en profiter pour la mettre en oeuvre dès à présent. Le système de tâches de symfony expose un objet d'envoi d'email, le mailer, par l'intermédiaire de la méthode sfCommandApplicationTask::getMailer(). De cette manière, l'accès à cet objet simplifie l'envoi d'emails. public function execute($arguments = array(), $options = array()) { $mailer = $this->getMailer(); $mailer->composeAndSend($from, $recipient, $subject, $messageBody); } Comme la configuration du gestionnaire d'envoi d'emails est lue depuis la configuration de l'application, la tâche doit obligatoirement accueillir une option application. Si la stratégie de spool est configurée pour gérer l'envoi des emails dans un projet, alors ces derniers ne seront envoyés qu'après l'exécution de la tâche project:send-emails. Dans la plupart des cas, le contenu du message ne se trouvera pas par magie dans la variable $messageBody, et devra donc être généré. Il n'existe pas de solution miracle pour générer le contenu destiné à alimenter des emails. En revanche, les développeurs ont la possibilité de s'appuyer sur quelques astuces en vue de se faciliter la vie. Déléguer la Génération du Contenu La génération d'un contenu d'email peut être déléguée simplement à une méthode protégée de la classe. Cette dernière se charge de générer puis de retourner ce contenu pour l'email à expédier. public function execute($arguments = array(), $options = array()) { $this->getMailer()->composeAndsend($from, $recipient, $subject, $this>getMessageBody()); } protected function getMessageBody() { return 'Hello, World'; } Utiliser le Plugin de Décoration de Swift Mailer Swift Mailer délivre un plugin intitulé Decorator. Il s'agit d'un moteur de templating basique à la fois simple et efficace qui accepte des chaînes de remplacement clé / valeurs. Ces dernières sont applicables spécifiquement à chaque destinataire quels que soient les emails à expédier. Le lecteur est inviter à consulter la documentation officielle de Swift Mailer pour plus d'informations à ce sujet. Utiliser une Librairie Externe de Template Intégrer une librairie tierce de rendu de template est relativement facile. Il suffit par exemple de s'appuyer sur le nouveau composant de templating intégré au projet Symfony Components. http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework Pour ce faire, les fichiers sources du composant doivent être téléchargés et déposés quelque part dans le projet. Le répertoire lib/vendor/templating est un excellent candidat pour cela. Enfin, il ne reste plus qu'à ajouter le code suivant à la tâche. protected function getMessageBody($template, $vars = array()) { $engine = $this->getTemplateEngine(); return $engine->render($template, $vars); } protected function getTemplateEngine() { if (is_null($this->templateEngine)) { $loader = new sfTemplateLoaderFilesystem(sfConfig::get('sf_app_dir').'/templates/emails/%s.php'); $this->templateEngine = new sfTemplateEngine($loader); } return $this->templateEngine; } Obtenir le Meilleur des Deux Mondes Il existe d'autres issues supplémentaires à découvrir pour obtenir un système parfait. Le plugin Decorator de Swift Mailer est particulièrement efficace lorsqu'il s'agit de gérer des remplacements par expéditeur. En effet, ce plugin permet de définir des remplacements pour chacun des destinataires afin que Swift Mailer puisse remplacer les jetons par les bonnes valeurs en s'appuyant sur le destinataire du mail à expédier. Le code ci-dessous explique comment intégrer ce fonctionnement à l'aide du composant de templating. public function execute($arguments = array(), $options = array()) { $message = Swift_Message::newInstance(); // fetches a list of users foreach($users as $user) { $replacements[$user->getEmail()] = array( '{username}' => $user->getEmail(), '{specific_data}' => $user->getSomeUserSpecificData(), ); $message->addTo($user->getEmail()); } $this->registerDecorator($replacements); $message ->setSubject('User specific data for {username}!') ->setBody($this->getMessageBody('user_specific_data')); $this->getMailer()->send($message); } protected function registerDecorator($replacements) { $this->getMailer()->registerPlugin(new Swift_Plugins_DecoratorPlugin($replacements)); } protected function getMessageBody($template, $vars = array()) { $engine = $this->getTemplateEngine(); return $engine->render($template, $vars); } protected function getTemplateEngine($replacements = array()) { http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework if (is_null($this->templateEngine)) { $loader = new sfTemplateLoaderFilesystem(sfConfig::get('sf_app_template_dir').'/emails/%s.php'); $this->templateEngine = new sfTemplateEngine($loader); } return $this->templateEngine; } Le fichier apps/frontend/templates/emails/user_specific_data.php embarque le contenu suivant. Hi {username}! We just wanted to let you know your specific data: {specific_data} Et c'est tout ! A présent, l'application bénéficie d'un moteur de templates entièrement fonctionnel et dédié à la génération de contenus d'email. Générer des URLs Composer des emails implique généralement de générer des URLs basées sur la configuration du routage. Heureusement, la génération des URLs a été simplifiée dans symfony 1.3 depuis qu'il est possible d'accéder au routage de l'application courante à l'intérieur de la tâche. Cette dernière expose en effet la méthode sfCommandApplicationTask::getRouting() prévue à cet effet. public function execute($arguments = array(), $options = array()) { $routing = $this->getRouting(); } Dans la mesure où le routage est dépendant de l'application, il convient de s'assurer que l'application dispose d'une configuration d'application disponible ; autrement il sera impossible de générer des URLs en utilisant le routage. Consultez la section concernant les Options Spéciales pour savoir comment définir automatiquement une configuration d'application dans la tâche. Maintenant que la tâche possède une instance du routage, la génération des URLs s'en voit simplifiée grâce à la méthode generate(). public function execute($arguments = array(), $options = array()) { $url = $this->getRouting()->generate('default', array('module' => 'foo', 'action' => 'bar')); } Le premier argument est le nom de la route et le second un tableau des paramètres pour celleci. A ce stade, symfony génère une URL relative, et c'est malheureusement ce dont on n'a pas besoin. En effet, la génération des URLs absolues depuis une tâche ne fonctionnera pas dans la mesure où il n'existe pas d'objet sfWebRequest sur qui compter pour récupérer l'hôte HTTP. Une manière triviale pour résoudre ce problème consiste à définir l'hôte HTTP en dur dans le fichier de configuration factories.yml. all: routing: class: sfPatternRouting param: generate_shortest_url: true extra_parameters_as_query_string: true context: host: example.org Il s'agit ici de remarquer le paramètre de configuration context_host. C'est à partir de celui-ci que l'objet de routage est capable de générer une URL absolue. http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework public function execute($arguments = array(), $options = array()) { $url = $this->getRouting()->generate('my_route', array(), true); } Accéder au Système d'Internationalisation - I18N Toutes les factories ne sont pas aussi facilement accessibles que le gestionnaire d'envoi d'email ou le routage. Lorsqu'il s'agit d'utiliser l'une d'entre elles, il n'est finalement pas si compliqué de les instancier à la main directement dans la tâche. Par exemple, si le développeur souhaite internationaliser les tâches, alors il devra accéder au système d'i18n de symfony. Pour ce faire, il convient de s'aider de la classe sfFactoryConfigHandler. protected function getI18N($culture = 'en') { if (!$this->i18n) { $config = sfFactoryConfigHandler::getConfiguration($this->configuration>getConfigPaths('config/factories.yml')); $class = $config['i18n']['class']; $this->i18n = new $class($this->configuration, null, $config['i18n']['param']); } $this->i18n->setCulture($culture); return $this->i18n; } Que se passe-t-il ici ? Tout d'abord, le code emploie une technique simple de manipulation du cache dans le but d'éviter la reconstruction de l'objet i18n à chaque appel. Ensuite, l'objet sfFactoryConfigHandler se charge de retrouver la configuration du composant afin de l'instancier avant de terminer par la définition de la culture. Désormais, la tâche est capable d'accéder à l'internationalisation comme le montre l'exemple suivant. public function execute($arguments = array(), $options = array()) { $this->log($this->getI18N('fr')->__('some translated text!')); } Bien sûr, passer à chaque fois la culture à la méthode n'est pas très pratique, surtout quand il s'agit de changer fréquemment la culture dans la tâche. La section suivante explique comment arranger cela. Remanier les Tâches La génération des contenus d'emails, l'expédition des ces derniers et la génération d'URLs sont des fonctionnalités communes à d'autres besoins. Par conséquent, il s'avère judicieux de créer une classe de base dans laquelle stocker ces fonctionnalités afin de les rendre disponibles aux classes dérivées. L'implémentation technique est quant à elle triviale puisqu'il suffit de créer une nouvelle classe de base dans un fichier lib/task/sfBaseEmailTask.class.php du projet et de lui inclure le contenu suivant. // lib/task/sfBaseEmailTask.class.php class sfBaseEmailTask extends sfBaseTask { protected function registerDecorator($replacements) { $this->getMailer()->registerPlugin(new Swift_Plugins_DecoratorPlugin($replacements)); } protected function getMessageBody($template, $vars = array()) { $engine = $this->getTemplateEngine(); return $engine->render($template, $vars); } protected function getTemplateEngine($replacements = array()) http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework { if (is_null($this->templateEngine)) { $loader = new sfTemplateLoaderFilesystem(sfConfig::get('sf_app_template_dir').'/templates/emails/%s.p ; $this->templateEngine = new sfTemplateEngine($loader); } return $this->templateEngine; } } Tant qu'à faire, il est aussi pertinent d'automatiser la configuration des options de la tâche en ajoutant ces méthodes à la classe sfBaseEmailTask. public function configure() { $this->addOption('application', null, sfCommandOption::PARAMETER_REQUIRED, 'The application', 'frontend'); } protected function generateUrl($route, $params = array()) { return $this->getRouting()->generate($route, $params, true); } Ce code utilise la méthode configure() pour ajouter des options communes à toutes les tâches dérivées. Malheureusement, toutes les classes dérivées de sfBaseEmailTask devront appeler parent::configure() dans leur propre méthode configure(). Cependant, il s'agit d'une gêne infime au regard de la véritable valeur ajoutée du code. Il convient à présent de remanier le code d'accès à l'objet I18N de la section précédente. public function configure() { $this->addOption('application', null, sfCommandOption::PARAMETER_REQUIRED, 'The application', 'frontend'); $this->addOption('culture', null, sfCommandOption::PARAMETER_REQUIRED, 'The culture', 'en'); } protected function getI18N() { if (!$this->i18n) { $config = sfFactoryConfigHandler::getConfiguration($this->configuration>getConfigPaths('config/factories.yml')); $class = $config['i18n']['class']; $this->i18n = new $class($this->configuration, null, $config['i18n']['param']); $this->i18n->setCulture($this->commandManager->getOptionValue('culture')); } return $this->i18n; } protected function changeCulture($culture) { $this->getI18N()->setCulture($culture); } protected function process(sfCommandManager $commandManager, $options) { parent::process($commandManager, $options); $this->commandManager = $commandManager; } Il existe un nouveau problème à résoudre à ce stade. En effet, il est impossible d'accéder aux valeurs des arguments et des options en dehors du périmètre de la méthode execute(). Pour corriger cela, la méthode process() doit être surchargée afin de rattacher le gestionnaire http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework d'options à la classe. Le gestionnaire d'options gère, comme son nom l'indique, les arguments et les options pour la classe courante. Par exemple, les valeurs des options peuvent être accédées à partir de la méthode getOptionValue(). Exécuter une Tâche dans une Autre Une manière alternative de factoriser les tâches consiste à embarquer une tâche dans une autre. Cette pratique est d'autant plus facilitée grâce aux méthodes sfCommandApplicationTask::createTask() et sfCommandApplicationTask::runTask(). La méthode createTask() instanciera la classe à la place du développeur en lui passant le nom de la tâche, de la même manière que la ligne de commande. En retour, cette méthode renverra une instance de la classe désirée prête à être exécutée. $task = $this->createTask('cache:clear'); $task->run(); Mais finalement, pour les plus paresseux, la tâche runTask() se charge de faire tout le travail en une seule passe. $this->runTask('cache:clear'); Il est bien sûr possible de passer des arguments et des options à la tâche en respectant l'ordre suivant. $this->runTask('plugin:install', array('sfGuardPlugin'), array('install_deps' => true)); Embarquer des tâches est aussi très utile pour composer des tâches plus puissantes à partir de tâches simples. Par exemple, la combinaison de plusieurs tâches dans une seule project:clean permettrait ainsi d'exécuter un ensemble d'opérations juste après le déploiement de l'application sur le serveur de production. $tasks = array( 'cache:clear', 'project:permissions', 'log:rotate', 'plugin:publish-assets', 'doctrine:build-model', 'doctrine:build-forms', 'doctrine:build-filters', 'project:optimize', 'project:enable', ); foreach($tasks as $task) { $this->run($task); } Manipuler le Système de Fichiers Symfony est livré par défaut avec une abstraction simple du système de fichiers (sfFilesystem) qui autorise l'exécution d'opérations simples sur les fichiers et les répertoires. Elle est accessible à l'intérieur de chaque tâche à l'aide de $this->getFilesystem(). Cette abstraction expose les méthodes suivantes : sfFilesystem::copy() copie un fichier ; sfFilesystem::mkdirs() crée une arborescence de répertoires récursivement ; sfFilesystem::touch() crée un fichier ; sfFilesystem::remove() supprime un fichier ou un répertoire ; sfFilesystem::chmod() change les permissions d'un fichier ou d'un répertoire ; sfFilesystem::rename() renomme un fichier ou un répertoire ; sfFilesystem::symlink() crée un lien symbolique ; sfFilesystem::relativeSymlink() crée un lien symbolique relatif à un répertoire ; sfFilesystem::mirror() duplique une arborescence complète ; sfFilesystem::execute() exécute une ligne de commande shell arbitraire. Elle expose également une méthode très pratique étudiée dans la prochaine section de ce http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework chapitre : replaceTokens(). Utiliser des Squelettes pour Générer des Fichiers Un autre usage courant des tâches consiste à générer des fichiers. La génération de fichiers peut être réalisée à partir de squelettes et la méthode mentionnée juste avant : sfFilesystem::replaceTokens(). Comme son nom l'évoque, cette méthode remplace des jetons à l'intérieur d'un jeu de fichiers. Concrètement, il suffit de lui fournir un tableau de fichiers ainsi qu'une liste de jetons afin qu'elle puisse remplacer toutes les occurrences de chaque jeton par sa valeur respective dans chaque fichier. Pour mieux comprendre tout l'intérêt de cette méthode, le prochain exemple réécrira partiellement une tâche existante : generate:module. Par souci de clarté et de sobriété, il s'agit seulement de regarder de plus près une partie de la méthode execute() de cette tâche, en supposant qu'elle a été correctement configurée avec les options nécessaires. La validation est quant à elle totalement ignorée. Avant de démarrer l'écriture de la tâche, il est nécessaire de créer un squelette pour les répertoires et les fichiers que la tâche devra générer, puis de les stocker quelque part. Par exemple dans data/skeleton/ : data/skeleton/ module/ actions/ actions.class.php templates/ Le squelette du fichier actions.class.php pourrait ressembler à celui ci-dessous : class %moduleName%Actions extends %baseActionsClass% { } La première étape de cette tâche consiste à dupliquer l'arborescence de fichiers au bon endroit. $moduleDir = sfConfig::get('sf_app_module_dir').$options['module']; $finder = sfFinder::type('any'); $this->getFilesystem()->mirror(sfConfig::get('sf_data_dir').'/skeleton/module', $moduleDir, $finder); A présent, il convient de remplacer les jetons du fichier actions.class.php : $tokens = array( 'moduleName' => $options['module'], 'baseActionsClass' => $options['base-class'], ); $finder = sfFinder::type('file'); $this->getFilesystem()->replaceTokens($finder->in($moduleDir), '%', '%', $tokens); Et c'est finalement tout ce dont a besoin le corps de la commande pour générer le nouveau module en utilisant le remplacement de jetons. La tâche native actuelle generate:module recherche dans data/skeleton/ d'autres squelettes alternatifs à utiliser au lieu de ceux par défaut. Donc faites attention ! Utiliser une Option Dry-Run Il arrive souvent que l'on veuille prévisualiser le résultat final de l'exécution d'une tâche avant même de l'avoir réellement lancée. Les lignes suivantes donnent quelques pistes et astuces sur la manière de procéder. Tout d'abord, il convient d'utiliser un nom standard, tel que dry-run, pour la nouvelle option. Tout le monde sera ainsi capable de reconnaître de quoi il s'agit. Avant symfony 1.3, la classe sfCommandApplication ajoutait une option dry-run par défaut. Cependant, celle-ci est désormais supprimée et doit être ajoutée à la main. Cette option trouve typiquement sa place dans une classe de base, comme cela a été expliqué plus haut. $this->addOption(new sfCommandOption('dry-run', null, sfCommandOption::PARAMETER_NONE, 'Executes a dry run'); Ensuite, la tâche doit être appelée de la sorte : http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework ./symfony my:task --dry-run L'option dry-run indique que la tâche ne devrait faire aucun changement. Ne devrait faire aucun changement sont les mots-clés importants dont il faut se souvenir. Lorsque la tâche est en cours d'exécution en mode dry-run, elle doit laisser l'environnement exactement dans le même état initial, y compris (mais ce n'est pas limité) : La base de données : ne pas insérer, ni ne mettre à jour, ni ne supprimer des enregistrements des tables. Une bonne pratique consiste à exécuter ce type d'opérations à l'intérieur d'une transaction qui sera annulée à la fin. Le système de fichiers : ne pas créer, ni ne modifier, ni ne supprimer des fichiers sur le système de fichiers. L'envoi d'emails : ne pas envoyer les emails, ou bien les expédier à une seule et même adresse de maintenance. Le code suivant illustre le fonctionnement du mode dry-run avec une base de données. $connection->beginTransaction(); // modify your database if ($options['dry-run']) { $connection->rollBack(); } else { $connection->commit(); } Ecrire des Tests Unitaires Dans la mesure où les tâches peuvent accomplir une variété de buts différents, les tester unitairement n'est pas une mince affaire non plus. En l'état, il n'existe pas de manière commune et uniforme de tester des tâches, mais il y'a pourtant quelques principes à suivre pour aider à rendre les classes de tests plus facilement testables. Tout d'abord, il est important de penser les tâches comme des contrôleurs. A cette occasion, il est bon de rappeler la règle d'or à propos des contrôleurs. Des contrôleurs légers et fins mais des modèles lourds et chargés. C'est tout ! Par conséquent, il convient de déplacer toute la logique métier à l'intérieur des classes de modèle. De cette manière, ce seront les classes de modèle qui devront être testées en priorité sur les classes de tâches, ce qui facilite les choses. Lorsqu'il n'est plus possible de déplacer davantage de logique métier à l'intérieur des modèles, une bonne pratique consiste à découper la méthode execute() en petites unités de code testable, chacune résidant dans sa propre méthode accessible (comprendre publiquement). Découper le code a plusieurs avantages : 1. La méthode execute() de la tâche en devient plus lisible ; 2. La tâche est quant à elle plus facilement testable ; 3. La tâche est enfin plus ouverte à d'éventuelles extensions. Il ne faut pas hésiter à être créatif et à se construire son propre petit environnement de tests pour ses besoins. Et si finalement, dans le cas où il n'existe aucun moyen de tester une tâche fraîchement développée, c'est qu'il existe deux issues différentes. La première c'est que certainement la tâche a été mal écrite tandis que la seconde consiste à demander son opinion à quelqu'un d'autre. Bien évidemment, il est aussi recommandé de ne pas hésiter à se plonger soi-même dans le code de quelqu'un d'autre afin d'étudier comment les choses ont été testées. C'est le cas par exemple des tâches de symfony et des générateurs qui sont relativement bien testés. Methodes Helper : Logging Le système de tâches de symfony essaie autant que possible de rendre la vie du développeur plus facile, en lui fournissant de nombreuses méthodes "helper" pratiques. Ces dernières sont responsables de la réalisation d'opérations communes telles que l'enregistrement de logs et les interactions avec l'utilisateur. http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework L'une d'elles peut facilement enregistrer des messages vers STDOUT en utilisant les méthodes de la famille log : log() accepte un tableau de messages ; logSection() est un peu plus élaborée puisqu'elle permet de formater les messages à l'aide d'un préfixe (premier argument) et un type de message (quatrième argument). Quand il s'agit d'enregistrer quelque chose de trop long, comme un nom de fichier, la méthode logSection() tronquera le message, ce qui peut s'avérer contraignant. Le troisième argument permet de définir la longueur maximale autorisée du message ; logBlock() est le style de log utilisé pour les exceptions. Une fois de plus il est possible de passer un style de formatage particulier. Les formats de logs disponibles sont ERROR, INFO, COMMENT et QUESTION, et il ne faut surtout pas se priver de les essayer tous afin d'étudier à quoi ils servent. Exemple d'utilisation : $this->logSection('file+', $aVeryLongFileName, $this->strlen($aVeryLongFileName)); $this->logBlock('Congratulations! You ran the task successfuly!', 'INFO'); Méthodes Helper : Interaction avec l'Utilisateur Il existe trois autres helpers qui sont fournis pour faciliter les interactions avec l'utilisateur : ask() imprime simplement une question et retourne l'entrée saisie par l'utilisateur ; askConfirmation() est similaire à ask() à la différence qu'une confirmation est demandée à l'utilisateur en plus, incluant les réponses y (yes, oui) et n (no, non) en guise d'aide à la saisie ; askAndValidate() est une méthode très utile qui imprime une question et valide la saisie de l'utilisateur à l'aide d'un validateur sfValidator passé en second argument. Le troisième argument est un tableau des options dans lequel peuvent être passées une valeur par défaut (value), un nombre maximum d'essais (attempts) ainsi qu'un style de formatage (style). Par exemple, il est possible de demander à l'utilisateur de saisir son adresse email et de la valider à la volée. $email = $this->askAndValidate('What is your email address?', new sfValidatorEmail()); Bonus : Utiliser les Tâches avec une Crontab La plupart des systèmes UNIX et GNU / Linux supportent la planification de tâches à travers un mécanisme connu sous le nom de cron. Le cron vérifie un fichier de configuration (un crontab) pour les commandes à exécuter à un certain temps ou bien à une certaine période. Les tâches de symfony peuvent facilement être intégrées dans un crontab, et la tâche project:sendemails est le candidat parfait pour un exemple de ce type. MAILTO="[email protected]" 0 3 * * * /usr/bin/php /var/www/yourproject/symfony project:send-emails Cette configuration indique au cron d'exécuter la tâche project:send-emails tous les jours à trois heures du matin et d'envoyer toutes les sorties possibles (ici les logs, erreurs, etc) à l'adresse email [email protected]. Pour plus d'informations sur le format du fichier de configuration de la crontab, il suffit de taper man 5 crontab dans un terminal de commandes. Il est aussi possible, et ça devrait être le cas ici, de passer des arguments et des options. MAILTO="[email protected]" 0 3 * * * /usr/bin/php /var/www/yourproject/symfony project:send-emails -env=prod --application=frontend La valeur /usr/bin/php est à remplacer par le chemin absolu vers le binaire PHP CLI. Si vous ne trouvez pas cette information, vous pouvez essayer la commande which php sur les systèmes Linux ou bien whereis php sur la plupart des autres systèmes UNIX. http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework Bonus : Utiliser STDIN Dans la mesure où les tâches sont exécutées dans un environnement en ligne de commande, alors le flux de l'entrée standard (STDIN) peut être atteint. La ligne de commande UNIX permet aux applications d'interagir entre elles par une variété de moyens, dont l'une d'elles est le pipe, symbolisé par le caractère |. Le pipe permet de passer la sortie d'une application (connue sous le nom de STDOUT) à une entrée standard d'une autre application (connue sous le nom de STDIN). Ces deux valeurs sont accessibles dans les tâches à travers les deux constantes de PHP STDIN et STDOUT. Il existe également un troisième flux standard, STDERR, accessible depuis STDERR, et qui vise à porter les messages d'erreur d'une application. Qu'est-il possible de faire exactement avec l'entrée standard ? Eh bien, il s'agit d'imaginer qu'il existe une application en cours d'exécution sur le serveur qui souhaiterait communiquer avec l'application symfony. Une méthode consiste bien sûr à les faire communiquer à travers HTTP, mais un moyen plus efficace serait de rediriger sa sortie vers une tâche symfony. On peut supposer par exemple qu'une application soit capable d'envoyer des données structurées (par exemple, un tableau PHP sérialisé), décrivant des objets de nom de domaine. On souhaite ensuite insérer ces derniers en base de données. Par conséquent, il s'agirait d'écrire la tâche suivante. while ($content = trim(fgets(STDIN))) { if ($data = unserialize($content) !== false) { $object = new Object(); $object->fromArray($data); $object->save(); } } Il ne resterait alors plus qu'à l'utiliser de la manière suivante. /usr/bin/data_provider | ./symfony data:import La chaîne data_provider consiste en l'application qui fournit les nouveaux objets de nom de domaine, tandis que data:import est la tâche qui vient tout juste d'être écrite. Conclusion Toutes les tâches à accomplir ne sont finalement limitées que par l'imagination du développeur. Le système de tâches de symfony est à la fois puissant et suffisamment flexible, ce qui permet à n'importe quel développeur de réaliser presque tout ce dont il a besoin. Ajouter à cela la puissance d'un shell UNIX, et les tâches ne seront plus qu'un jeu d'enfant. « Développer pour Facebook Manipuler le Cache de Configuration de symfony » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] The More with symfony book | Tirer Profit de la Ligne de Commande | symfony | Web PHP Framework > More http://www.symfony-project.org/more-with-symfony/1_4/fr/13-Leveraging-the-Power-of-the-Command-Line[31/12/2010 02:35:07] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Manipuler le Cache de Configuration de symfony You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Kris Wallsmith Un de mes principaux intérêts en tant que développeur symfony est de suivre le travail de la communauté autant que possible quel que soit le type de projet. Bien que je connaisse sur le bout des doigts le code source interne de symfony, ce n'est pas nécessairement une obligation pour tous les développeurs. Heureusement, symfony fournit des solutions capables d'isoler chaque composant d'une application, permettant ainsi à n'importe qui d'effectuer des modifications sans difficulté. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Les Chaînes de Caractères dans les Formulaires Un excellent exemple pour illustrer ces propos est le framework de formulaires. Le framework de formulaires est un puissant outil de symfony qui transforme le rendu et la validation d'un formulaire en objets PHP, dans le but de donner aux développeurs davantage de contrôle sur leur gestion. Le travail des développeurs en est ainsi largement simplifié. En effet, ces derniers peuvent ainsi encapsuler une logique complexe dans une seule classe de formulaire, étendre cette dernière et la réutiliser à différents endroits du code. Cependant, pour un intégrateur, cette abstraction de rendu du formulaire peut être quelque peu troublante. Il suffit d'étudier l'implémentation du formulaire suivant pour s'en convaincre. La classe qui configure ce formulaire est de la forme suivante : // lib/form/CommentForm.class.php class CommentForm extends BaseForm { public function configure() { $this->setWidget('body', new sfWidgetFormTextarea()); $this->setValidator('body', new sfValidatorString(array('min_length' => 12))); } } Le formulaire est ensuite rendu grâce au template PHP suivant : <!-- apps/frontend/modules/main/templates/indexSuccess.php --> <form action="#" method="post"> <ul> <li> <?php echo $form['body']->renderLabel() ?> http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] Chapter Content Les Chaînes de Caractères dans les Formulaires Une Solution : le YAML Filtrer les Variables de Template Charger le YAML Le Cache de Configuration Intégration dans le Code par les Tests Les Gestionnaires de Configuration Personnalisés Jouer avec les Formulaires Imbriqués Qu'en est-il des Performances ? Bonus : Embarquer le Gestionnaire de Configuration dans un Plugin Conclusion The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework <?php echo $form['body'] ?> <?php echo $form['body']->renderError() ?> </li> </ul> <p><button type="submit">Post your comment now</button></p> </form> L'intégrateur a certes la possibilité de modifier le rendu du formulaire. Il peut par exemple modifier les intitulés par défaut. <?php echo $form['body']->renderLabel('Please enter your comment') ?> Une classe CSS peut également être ajoutée lors du rendu des champs. <?php echo $form['body']->render(array('class' => 'comment')) ?> Ces modifications sont intuitives et faciles. Mais qu'en est-il s'il doit modifier des messages d'erreur ? Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Search powered by google La méthode renderError() n'accepte aucun argument. La seule solution actuelle pour l'intégrateur consiste à ouvrir la classe relative au formulaire, puis de trouver la méthode correspondant à la validation afin d'en modifier les paramètres. Dans l'exemple précédent, les modifications suivantes seraient nécessaires. // before $this->setValidator('body', new sfValidatorString(array('min_length' => 12))); // after $this->setValidator('body', new sfValidatorString(array('min_length' => 12), array( 'min_length' => 'You haven't written enough', ))); Où est l'intrus ? Ici c'est une apostrophe dans une chaine de caractères entourée de guillemets simples qui a été utilisée. Un développeur avisé ne ferait jamais une pareille erreur, mais qu'en est-il d'un intégrateur qui doit plonger dans une classe de formulaire ? Il ne le fera pas. La question qui se pose alors est la suivante. Faut-il sérieusement attendre d'un intégrateur de connaître suffisamment bien le framework de formulaires au point de trouver à quel endroit se définit un message d'erreur ? Est-ce que quelqu'un d'habitué à modifier des templates doit connaître la signature d'un constructeur de validateur ? La réponse à ces questions est clairement non. Les intégrateurs réalisent déjà beaucoup de travail et il serait complètement déraisonnable de penser que quelqu'un qui n'a pas l'habitude d'écrire du code puisse apprendre les rouages du framework de formulaires de symfony. Une Solution : le YAML Afin de simplifier l'édition de ces chaines de caractères, une couche de configuration en YAML sera développée dans le but d'améliorer chaque objet de formulaire passé à la vue. Le fichier de configuration prendra la forme suivante : # config/forms.yml CommentForm: body: label: Please enter your comment attributes: { class: comment } http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework errors: min_length: You haven't written enough C'est tout de même beaucoup plus simple. La configuration parle d'elle même, et résout le problème précédent de guillemets. Il s'agit maintenant d'écrire le code nécessaire à l'intégration de ce YAML. Filtrer les Variables de Template La première difficulté consiste à trouver un hameçon dans symfony qui permette de filtrer chaque variable de formulaire passée au template par le fichier de configuration. La solution consiste à utiliser l'événement template.filter_parameters qui est appelé par le coeur de symfony juste avant de rendre un template ou un partiel. // lib/form/sfFormYamlEnhancer.class.php class sfFormYamlEnhancer { public function connect(sfEventDispatcher $dispatcher) { $dispatcher->connect('template.filter_parameters', array($this, 'filterParameters')); } public function filterParameters(sfEvent $event, $parameters) { foreach ($parameters as $name => $parameter) { if ($parameter instanceof sfForm && !$parameter->getOption('is_enhanced')) { $this->enhance($parameter); $parameter->setOption('is_enhanced', true); } } return $parameters; } public function enhance(sfForm $form) { // ... } } Ce code vérifie si une option is_enhanced existe pour chaque objet de >formulaire avant de le modifier. Ceci afin d'éviter que les formulaires qui sont chargés depuis un partiel soient modifiés deux fois. Cette classe doit maintenant être chargée depuis le fichier de configuration de l'application. // apps/frontend/config/frontendConfiguration.class.php class frontendConfiguration extends sfApplicationConfiguration { public function initialize() { $enhancer = new sfFormYamlEnhancer($this->getConfigCache()); $enhancer->connect($this->dispatcher); } } Désormais les variables de formulaire peuvent être isolées juste avant d'être passées au template ou au partiel. Tous les outils nécessaires à ce fonctionnement sont en plus disponibles. La dernière étape consiste enfin à appliquer ce qui a été configuré dans le YAML. Charger le YAML La manière la plus simple d'appliquer le YAML à chaque formulaire est de le charger dans un tableau et d'itérer dessus pour chaque configuration. public function enhance(sfForm $form) { $config = sfYaml::load(sfConfig::get('sf_config_dir').'/forms.yml'); http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework foreach ($config as $class => $fieldConfigs) { if ($form instanceof $class) { foreach ($fieldConfigs as $fieldName => $fieldConfig) { if (isset($form[$fieldName])) { if (isset($fieldConfig['label'])) { $form->getWidget($fieldName)->setLabel($fieldConfig['label']); } if (isset($fieldConfig['attributes'])) { $form->getWidget($fieldName)->setAttributes(array_merge( $form->getWidget($fieldName)->getAttributes(), $fieldConfig['attributes'] )); } if (isset($fieldConfig['errors'])) { foreach ($fieldConfig['errors'] as $errorCode => $errorMessage) { $form->getValidator($fieldName)->setMessage($errorCode, $errorMessage); } } } } } } } Cependant, cette implémentation a de nombreux défauts. Tout d'abord, le YAML est lu depuis le système de fichiers et chargé dans l'objet sfYaml à chaque appel. Lire depuis le système de fichiers de cette manière doit être évité pour des raisons évidentes de performances. Ensuite, il existe plusieurs niveaux de boucles imbriquées et beaucoup trop de conditions qui ralentissent inutilement l'exécution de l'application. La solution pour résoudre ces soucis réside dans la gestion du cache de configuration de symfony. Le Cache de Configuration Derrière le cache de configuration se trouve une collection de classes qui optimisent l'utilisation du YAML en le transformant en code PHP et en le stockant dans le dossier de cache avant exécution. Ce mécanisme élimine la nécessité de charger le contenu de la configuration dans sfYaml avant de pouvoir en utiliser les valeurs. L'étape suivante consiste à implémenter ce système pour la classe de formulaire. Au lieu de charger le fichier forms.yml dans sfYaml, il s'agit de demander, au système de configuration une version pré-chargée en objet PHP. Pour ce faire, la classe sfFormYamlEnhancer aura besoin d'accéder au cache de configuration, et c'est pour cette raison que cet objet sera passé dans le constructeur. class sfFormYamlEnhancer { protected $configCache = null; public function __construct(sfConfigCache $configCache) { $this->configCache = $configCache; $this->configCache->registerConfigHandler('config/forms.yml', 'sfSimpleYamlConfigHandler'); } // ... } Le cache de configuration a besoin de savoir ce qu'il doit faire lorsqu'un fichier de configuration http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework est appelé par l'application. Pour l'instant, il utilise la classe sfSimpleYamlConfigHandler pour charger le fichier forms.yml. Le YAML est donc analysé puis transformé en un tableau PHP, juste avant d'être mis en cache. A ce stade, la configuration est en place et prête à être chargée. Elle peut être appelée de la manière suivante à la place de sfYaml. public function enhance(sfForm $form) { $config = include $this->configCache->checkConfig('config/forms.yml'); // ... } C'est déjà beaucoup mieux. Non seulement la contrainte de devoir analyser le YAML à chaque requête a été éliminée, mais le code fait usage de la fonction native include() de PHP qui favorise la mise en cache du code. Développement vs. Environnement de production L'utilisation de checkConfig() diffère selon que le mode debug est activé ou pas. Dans l'environnement de production, quand le mode debug est désactivé, cette méthode fonctionne comme décrit ci-dessous : Vérification de l'existence d'un fichier caché pour le fichier demandé S'il existe, retourner le chemin du fichier caché S'il n'existe pas : Convertir le fichier de configuration ; Sauvegarder le code résultant dans le cache ; Retourner le chemin du nouveau fichier caché. Cette méthode fonctionne différemment lorsque le mode debug est activé. Les fichiers de configuration étant modifiés au cours du développement, la méthode checkConfig() compare les fichiers originaux et ceux mis en cache pour s'assurer d'avoir la dernière version. Ce processus inclut quelques vérifications : Vérification d'une version cachée du fichier demandé Si elle n'existe pas : Traiter le fichier de configuration ; Sauvegarder le code résultant dans le cache. Si elle existe : Comparer les dernières modifications de la configuration et des fichiers cachés ; Si le fichier de configuration a été modifié récemment : Traiter le fichier de configuration ; Sauvegarder le code résultant dans le cache. Retourner le chemin du fichier caché Intégration dans le Code par les Tests Avant d'aller plus loin dans les développements, il convient d'écrire quelques tests unitaires pour valider le fonctionnement de la classe sfFormYamlEnhancer. // test/unit/form/sfFormYamlEnhancerTest.php include dirname(__FILE__).'/../../bootstrap/unit.php'; $t = new lime_test(3); $configuration = $configuration->getApplicationConfiguration( 'frontend', 'test', true, null, $configuration->getEventDispatcher()); sfToolkit::clearDirectory(sfConfig::get('sf_app_cache_dir')); $enhancer = new sfFormYamlEnhancer($configuration->getConfigCache()); // ->enhance() $t->diag('->enhance()'); $form = new CommentForm(); http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework $form->bind(array('body' => '+1')); $enhancer->enhance($form); $t->like($form['body']->renderLabel(), '/Please enter your comment/', '->enhance() enhances labels'); $t->like($form['body']->render(), '/class="comment"/', '->enhance() enhances widgets'); $t->like($form['body']->renderError(), '/You haven\'t written enough/', '->enhance() enhances error messages'); L'exécution de cette suite de tests sur la version actuelle de la classe sfFormYamlEnhancer réussit et valide la conformité du code. Le code est désormais prêt à être modifié. Les tests unitaires avertiront le développeur si la moindre pièce est cassée dans le code. Les Gestionnaires de Configuration Personnalisés Dans le code ci-dessous, chaque variable de formulaire passée au template itèrera sur chaque classe de formulaire configurée dans le fichier forms.yml. Cette méthode fonctionne mais lorsqu'il s'agit de passer plusieurs objets de formulaire au template, ou bien une longue liste de formulaires configurés en YAML, un impact sur les performances de l'application se fera ressentir. C'est donc une excellente opportunité pour écrire un gestionnaire de configuration personnalisé qui optimisera ces performances. Pourquoi personnaliser ? Ecrire un gestionnaire de configuration personnalisé n'est pas des plus aisés. Tous les développeurs sont sujets à faire des erreurs et la testabilité de ces objets n'est pas chose facile non plus. Néanmoins, les bénéfices en seront substantiels. L'avantage de créer une logique personnalisée permet de bénéficier de la flexibilité du YAML et de la faible surcharge du code PHP natif. En ajoutant un cache d'opcodes (tel que APC ou XCache à tout cela, le gestionnaire de configuration sera difficile à battre en termes de facilité d'utilisation et de performances. L'essentiel de la magie de ces gestionnaires se passe en coulisses. Toute la logique est mise en cache avant d'exécuter n'importe quel gestionnaire de configuration. Par conséquent, le développeur a tout le loisir de se concentrer sur l'écriture du code nécessaire à la mise en oeuvre de la configuration YAML de l'application. Chaque gestionnaire doit implémenter les deux méthodes suivantes : static public function getConfiguration(array $configFiles) public function execute($configFiles) La première méthode statique getConfiguration() reçoit comme paramètre un tableau contenant le chemin des fichiers. Elle se charge ensuite de les analyser et de regrouper leur contenu en une seule valeur. Dans la classe sfSimpleYamlConfigHandler utilisée précédemment, cette méthode contient seulement une ligne. static public function getConfiguration(array $configFiles) { return self::parseYamls($configFiles); http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework } La classe sfSimpleYamlConfigHandler étend sfYamlConfigHandler qui inclut un certain nombre de méthodes servant au traitement du fichier de configuration YAML : ::parseYamls($configFiles) ::parseYaml($configFile) ::flattenConfiguration($config) ::flattenConfigurationWithEnvironment($config) Les deux premières méthodes implémentent le principe de configuration en cascade de symfony. Les deux suivantes implémentent la sensibilisation à l'environnement. La méthode statique getConfiguration() du gestionnaire aura besoin d'une méthode personnalisée afin de regrouper les configurations des classes dont elle hérite. Par conséquent, il convient d'écrire une méthode applyInheritance() qui appliquera cette logique. // lib/config/sfFormYamlEnhancementsConfigHandler.class.php class sfFormYamlEnhancementsConfigHandler extends sfYamlConfigHandler { public function execute($configFiles) { $config = self::getConfiguration($configFiles); // compile data $retval = "<?php\n". "// auto-generated by %s\n". "// date: %s\nreturn %s;\n"; $retval = sprintf($retval, __CLASS__, date('Y/m/d H:i:s'), var_export($config, true)); return $retval; } static public function getConfiguration(array $configFiles) { return self::applyInheritance(self::parseYamls($configFiles)); } static public function applyInheritance($config) { $classes = array_keys($config); $merged = array(); foreach ($classes as $class) { if (class_exists($class)) { $merged[$class] = $config[$class]; foreach (array_intersect(class_parents($class), $classes) as $parent) { $merged[$class] = sfToolkit::arrayDeepMerge($config[$parent], $merged[$class]); } } } return $merged; } } A présent, on dispose d'un tableau dont les valeurs ont été rassemblées en fonction de la classe héritée. Le besoin de devoir analyser la configuration en entier a été éliminée via un appel à instanceof pour vérifier le type de chaque objet. De plus, cette opération est effectuée dans le gestionnaire de configuration et ne sera donc exécutée qu'une fois avant la mise en cache. Ce tableau peut ainsi être passé à l'objet de formulaire de la sorte. class sfFormYamlEnhancer { http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework protected $configCache = null; public function __construct(sfConfigCache $configCache) { $this->configCache = $configCache; $this->configCache->registerConfigHandler('config/forms.yml', 'sfFormYamlEnhancementsConfigHandler'); } // ... public function enhance(sfForm $form) { $config = include $this->configCache->checkConfig('config/forms.yml'); $class = get_class($form); if (isset($config[$class])) { $fieldConfigs = $config[$class]; } else if ($overlap = array_intersect(class_parents($class), array_keys($config))) { $fieldConfigs = $config[current($overlap)]; } else { return; } foreach ($fieldConfigs as $fieldName => $fieldConfig) { // ... } } } Avant de relancer la suite de tests unitaires, il convient d'ajouter quelques lignes pour la nouvelle logique de classe. # config/forms.yml # ... BaseForm: body: errors: min_length: A base min_length message required: A base required message Il s'agit ici de vérifier que le nouveau message required est appliqué dans le test, et de confirmer que les enfants du formulaire recevront les améliorations de la classe parente. $t = new lime_test(5); // ... $form = new CommentForm(); $form->bind(); $enhancer->enhance($form); $t->like($form['body']->renderError(), '/A base required message/', '->enhance() considers inheritance'); class SpecialCommentForm extends CommentForm { } $form = new SpecialCommentForm(); $form->bind(); $enhancer->enhance($form); $t->like($form['body']->renderLabel(), '/Please enter your comment/', '->enhance() applies parent config'); L'exécution de la nouvelle mise à jour du test confirme que les modifications apportées au formulaire fonctionnent comme prévu. http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework Jouer avec les Formulaires Imbriqués Il existe une fonctionnalité importante dans le framework de formulaires de symfony qui n'a pas encore été discutée jusqu'ici : les formulaires imbriqués. Si une instance de CommentForm est imbriquée dans un autre formulaire, les améliorations apportées dans le fichier forms.yml ne fonctionneront plus. Un simple test unitaire suffit pour le démontrer. $t = new lime_test(6); // ... $form = new BaseForm(); $form->embedForm('comment', new CommentForm()); $form->bind(); $enhancer->enhance($form); $t->like($form['comment']['body']->renderLabel(), '/Please enter your comment/', '>enhance() enhances embedded forms'); Ces quelques lignes prouvent que les formulaires imbriqués ne sont pas gérés. Pour que le test fonctionne de nouveau, il faut développer une version plus avancée du gestionnaire de configuration. Il convient de trouver une solution pour implémenter les spécifications configurées dans le fichier forms.yml d'une manière plus modulaire afin de prendre en compte les formulaires imbriqués. Pour ce faire, une version personnalisée doit être écrite pour chaque méthode de chaque classe. Ces méthodes seront générées par le gestionnaire de configuration personnalisé dans une nouvelle classe métier. class sfFormYamlEnhancementsConfigHandler extends sfYamlConfigHandler { // ... protected function getEnhancerCode($fields) { http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework $code = array(); foreach ($fields as $field => $config) { $code[] = sprintf('if (isset($fields[%s]))', var_export($field, true)); $code[] = '{'; if (isset($config['label'])) { $code[] = sprintf(' $fields[%s]->getWidget()->setLabel(%s);', var_export($config['label'], true)); } if (isset($config['attributes'])) { $code[] = ' $fields[%s]->getWidget()->setAttributes(array_merge('; $code[] = ' $fields[%s]->getWidget()->getAttributes(),'; $code[] = ' '.var_export($config['attributes'], true); $code[] = ' ));'; } if (isset($config['errors'])) { $code[] = sprintf(' if ($error = $fields[%s]->getError())', var_export($field, true)); $code[] = ' {'; $code[] = ' $error->getValidator()->setMessages(array_merge('; $code[] = ' $error->getValidator()->getMessages(),'; $code[] = ' '.var_export($config['errors'], true); $code[] = ' ));'; $code[] = ' }'; } $code[] = '}'; } return implode(PHP_EOL.' ', $code); } } Il est important de remarquer ici que le tableau de configuration est vérifié pour certaines clés lors de la génération du code plutôt qu'à l'exécution afin de bénéficier d'un léger gain de performances. De manière générale, la logique qui vérifie les conditions de la configuration devrait être exécutée dans le gestionnaire de configuration, et non dans le code généré. La logique qui vérifie les conditions d'exécution, comme la nature de l'objet de formulaire, doit être appelée au moment de l'exécution du code. Le code généré est ensuite placé dans une définition de classe sauvegardée dans le répertoire de cache. class sfFormYamlEnhancementsConfigHandler extends sfYamlConfigHandler { public function execute($configFiles) { $forms = self::getConfiguration($configFiles); $code = array(); $code[] = '<?php'; $code[] = '// auto-generated by '.__CLASS__; $code[] = '// date: '.date('Y/m/d H:is'); $code[] = 'class sfFormYamlEnhancementsWorker'; $code[] = '{'; $code[] = ' static public $enhancable = '.var_export(array_keys($forms), true).';'; foreach ($forms as $class => $fields) { $code[] = ' static public function enhance'.$class.'(sfFormFieldSchema $fields)'; http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework $code[] = ' $code[] = ' $code[] = ' {'; '.$this->getEnhancerCode($fields); }'; } $code[] = '}'; return implode(PHP_EOL, $code); } // ... } La classe sfFormYamlEnhancer reportera la classe métier générée afin de gérer le traitement des objets de formulaire, mais elle doit maintenant prendre en compte la récursivité des formulaires imbriqués. Pour ce faire, il s'agit de traiter le schéma des champs de formulaire (sur lequel on peut itérer récursivement) et les objets de formulaire (y compris les formulaires imbriqués) en parallèle. class sfFormYamlEnhancer { // ... public function enhance(sfForm $form) { require_once $this->configCache->checkConfig('config/forms.yml'); $this->doEnhance($form->getFormFieldSchema(), $form); } protected function doEnhance(sfFormFieldSchema $fieldSchema, sfForm $form) { if ($enhancer = $this->getEnhancer(get_class($form))) { call_user_func($enhancer, $fieldSchema); } foreach ($form->getEmbeddedForms() as $name => $form) { if (isset($fieldSchema[$name])) { $this->doEnhance($fieldSchema[$name], $form); } } } public function getEnhancer($class) { if (in_array($class, sfFormYamlEnhancementsWorker::$enhancable)) { return array('sfFormYamlEnhancementsWorker', 'enhance'.$class); } else if ($overlap = array_intersect(class_parents($class), sfFormYamlEnhancementsWorker::$enhancable)) { return array('sfFormYamlEnhancementsWorker', 'enhance'.current($overlap)); } } } Une fois imbriqués, les champs d'un objet de formulaire ne devraient pas être modifiés. Les formulaires imbriqués sont déclarés dans le formulaire parent afin de faciliter le traitement, mais ils n'ont pas d'incidence sur le rendu de ce dernier. A ce stade les formulaires imbriqués sont enfin gérés et les tests devraient s'exécuter sans aucun souci comme le montre la capture d'écran ci-dessous. http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework Qu'en est-il des Performances ? Afin de s'assurer que tout le temps passé jusqu'à présent n'a pas été dépensé inutilement, une suite de tests de performance peut être exécutée. Quelques classes supplémentaires peuvent être ajoutées au fichier forms.yml grâce à une boucle PHP afin de rendre les résultats plus intéressants. # <?php for ($i = 0; $i < 100; $i++): ?> # Form<?php echo $i ?>: ~ # <?php endfor; ?> # C'est le morceau de code ci-dessous qui a pour rôle de générer toutes ces classes. mkdir($dir = sfConfig::get('sf_lib_dir').'/form/test_fixtures'); for ($i = 0; $i < 100; $i++) { file_put_contents($dir.'/Form'.$i.'.class.php', '<?php class Form'.$i.' extends BaseForm { }'); } Le benchmark est enfin prêt à être exécuter. Pour obtenir les résultats ci-dessous, la commande Apache suivante a été exécutée sur un Macbook à plusieurs reprises jusqu'à obtenir un écart standard de moins de 2 ms. $ ab -t 60 -n 20 http://localhost/config_cache/web/index.php Le premier benchmark de base ci-dessous exécute l'application par défaut sans les améliorations apportées. Il convient tout d'abord de commenter l'appel de sfFormYamlEnhancer dans le fichier frontendConfiguration, puis de relancer le test. Connection Times (ms) min mean[+/-sd] median Connect: 0 0 0.0 0 Processing: 62 63 1.5 63 Waiting: 62 63 1.5 63 Total: 62 63 1.5 63 max 0 69 69 69 A présent, il s'agit de copier la première version de sfFormYamlEnhancer::enhance() qui appelait directement sfYaml avant de relancer l'exécution du benchmark. Connection Times (ms) min mean[+/-sd] median Connect: 0 0 0.0 0 Processing: 87 88 1.6 88 Waiting: 87 88 1.6 88 Total: 87 88 1.7 88 max 0 93 93 94 Ces tests montrent un ralentissement de 25 ms en moyenne à chaque requête, soit une augmentation du temps d'exécution de près de 40%. Maintenant, il s'agit de modifier ces changements afin d'appeler la méthode enhance() pour que le gestionnaire de configuration personnalisé soit appelé. Connection Times (ms) min mean[+/-sd] median Connect: 0 0 0.0 0 max 0 http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework Processing: Waiting: Total: 62 62 62 63 63 64 1.6 1.6 1.6 63 63 63 70 70 70 On constate ici que le temps de traitement par défaut a été restauré en utilisant le gestionnaire de configuration par défaut. Bonus : Embarquer le Gestionnaire de Configuration dans un Plugin Maintenant que cet excellent système d'amélioration des objets de formulaire via une configuration YAML est développé, pourquoi ne pas l'embarquer dans un plugin, puis le partager avec la communauté. Cela peut paraître intimidant pour ceux qui n'ont jamais publié de plugin mais les quelques lignes qui suivent dissiperont ces craintes. Le plugin aura la structure suivante. sfFormYamlEnhancementsPlugin/ config/ sfFormYamlEnhancementsPluginConfiguration.class.php lib/ config/ sfFormYamlEnhancementsConfigHandler.class.php form/ sfFormYamlEnhancer.class.php test/ unit/ form/ sfFormYamlEnhancerTest.php Quelques modifications sont nécessaires afin de faciliter le processus d'installation du plugin. La création et la connexion de l'objet optimisé doivent être encapsulées dans la classe de configuration du plugin. class sfFormYamlEnhancementsPluginConfiguration extends sfPluginConfiguration { public function initialize() { if ($this->configuration instanceof sfApplicationConfiguration) { $enhancer = new sfFormYamlEnhancer($this->configuration->getConfigCache()); $enhancer->connect($this->dispatcher); } } } Le script de test doit aussi être mis à jour afin de prendre en compte le chemin relatif vers le script d'amorçage du projet. include dirname(__FILE__).'/../../../../../test/bootstrap/unit.php'; // ... Enfin, le plugin doit être activé dans la classe ProjectConfiguration. class ProjectConfiguration extends sfProjectConfiguration { public function setup() { $this->enablePlugins('sfFormYamlEnhancementsPlugin'); } } Pour exécuter les tests depuis le plugin, il suffit de connecter ces derniers depuis la classe de configuration ProjectConfiguration. class ProjectConfiguration extends sfProjectConfiguration { // ... public function setupPlugins() { $this->pluginConfigurations['sfFormYamlEnhancementsPlugin']->connectTests(); } http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework } Les tests doivent maintenant s'exécuter correctement lorsqu'ils sont appelés à l'aide des commandes test:*. Toutes les classes sont maintenant rangées dans la structure du plugin bien qu'un autre problème subsiste. Le script de test cherche toujours ces fichiers au niveau de l'arborescence du projet. Il faut donc isoler le code dans la classe spécialisée qui appelle la configuration du cache afin de surcharger la méthode dans le script de tests, et utiliser le fichier forms.yml. class sfFormYamlEnhancer { // ... public function enhance(sfForm $form) { $this->loadWorker(); $this->doEnhance($form->getFormFieldSchema(), $form); } public function loadWorker() { require_once $this->configCache->checkConfig('config/forms.yml'); } // ... } La méthode loadWorker() peut alors être surchargée afin d'appeler le gestionnaire de configuration personnalisé. La classe CommentForm doit aussi être déplacée dans le script de test et le fichier forms.yml dans la structure test/fixtures du plugin. include dirname(__FILE__).'/../../../../../test/bootstrap/unit.php'; $t = new lime_test(6); class sfFormYamlEnhancerTest extends sfFormYamlEnhancer { public function loadWorker() { if (!class_exists('sfFormYamlEnhancementsWorker', false)) { $configHandler = new sfFormYamlEnhancementsConfigHandler(); $code = $configHandler>execute(array(dirname(__FILE__).'/../../fixtures/forms.yml')); $file = tempnam(sys_get_temp_dir(), 'sfFormYamlEnhancementsWorker'); file_put_contents($file, $code); require $file; } } } class CommentForm extends BaseForm http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Manipuler le Cache de Configuration de symfony | symfony | Web PHP Framework { public function configure() { $this->setWidget('body', new sfWidgetFormTextarea()); $this->setValidator('body', new sfValidatorString(array('min_length' => 12))); } } $configuration = $configuration->getApplicationConfiguration( 'frontend', 'test', true, null, $configuration->getEventDispatcher()); $enhancer = new sfFormYamlEnhancerTest($configuration->getConfigCache()); // ... Enfin, la création du package du plugin est facilitée grâce au plugin sfTaskExtraPlugin qui délivre une tâche plugin:package. Après exécution de cette dernière et quelques questions posées dans la console, le plugin sera enfin prêt. $ php symfony plugin:package sfFormYamlEnhancementsPlugin Le code de cet article a été publié dans un plugin, et est disponible en téléchargement sur le site de symfony : http://symfony-project.org/plugins/sfFormYamlEnhancementsPlugin Ce plugin inclut tout ce qui a été abordé dans ce chapitre et bien plus encore. Il fournit un support pour les fichiers widgets.yml et validators.yml ainsi qu'une intégration avec la tâche i18n:extract afin de fournir une internationalisation plus aisée des formulaires. Conclusion Ce chapitre a permis de se rendre compte, grâce aux benchmarks exécutés, que la gestion du cache de configuration de symfony rend possible l'utilisation de fichiers YAML tout en préservant un impact limité sur les performances. « Tirer Profit de la Ligne de Commande Travailler avec la Communauté symfony » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/14-Playing-with-Symfony-Config-Cache[31/12/2010 02:35:11] The More with symfony book | Travailler avec la Communauté symfony | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Travailler avec la Communauté symfony You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Par Stefan Koopmanschap Choisir de travailler avec un projet open-source peut être motivé par différentes raisons : la gratuité ou l'accès au code source par exemple. Mais la raison principale réside bien souvent dans sa communauté. Dans le monde de l'open-source, il existe autant de communautés que de projets open-source. Concernant symfony, sa communauté est à ce jour très ouverte et conviviale. Mais comment bénéficier au mieux de cette communauté ? Et comment chacun peut y apporter sa propre contribution ? Ce chapitre présente la communauté symfony et les différents moyens d'y collaborer. Les entreprises comme les développeurs trouveront ainsi leur propre façon de participer. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Profiter au Mieux de la Communauté Il existe diverses façons de profiter de la communauté symfony. Le simple fait d'utiliser le framework est un bénéfice en soit, car même si, à l'origine, il a été développé et porté par Sensio, symfony ne serait pas là où il en est aujourd'hui sans la forte implication de sa communauté. Le Support Tous les développeurs, et plus particulièrement les débutants, se sont un jour ou l'autre déjà retrouvés bloqués sans savoir comment résoudre un problème. Heureusement, symfony possède une communauté active et accueillante qui se fera un plaisir de répondre à toutes les questions que vous pourriez vous poser. Avant de Poser une Question Avant de poser une question sur les différents moyens mis à votre disposition, prenez le temps de chercher une réponse par vous même. Vous pouvez bien sûr effectuer des recherches à l'aide de Google, mais il est recommandé de concentrer vos recherches sur les différentes listes de diffusion de symfony comme les archives de symfony-users. Poser une Question Cela peut paraître trivial mais il est important de savoir comment poser une question. Réfléchissez bien à ce que vous êtes sur le point de demander. Vérifiez tout d'abord que la réponse ne se trouve pas déjà dans la documentation officielle. Voici quelques conseils qui vous aideront à obtenir des réponses plus pertinentes : Réfléchissez à votre question. Assurez-vous de la formuler clairement. Expliquez ce que vous faites (ou bien essayez de le faire) et ce que vous n'arrivez pas à faire. N'oubliez pas d'indiquer les éventuelles erreurs que vous obtenez. Expliquez vos tentatives en indiquant les éléments sur lesquels vous vous êtes appuyés et les pistes dont vous disposez. Les Listes de Diffusion Plusieurs Groupes Google existent autour de symfony. Ces groupes sont le meilleur moyen d'entrer en contact avec les utilisateurs et les développeurs de symfony. Si vous êtes utilisateur de symfony, le groupe symfony users est le premier endroit pour rechercher de l'aide. Cette liste de diffusion regroupe à la fois des utilisateurs de symfony, mais aussi des débutants et la plupart des membres de la core team du framework. Par conséquent, il y aura toujours quelqu'un capable de répondre à votre question. Il existe aussi d'autres groupes destinés à d'autres sujets : symfony-devs pour les discussions concernant le développement du framework (pas de support !) ; http://www.symfony-project.org/more-with-symfony/1_4/fr/15-Working-with-the-Symfony-Community[31/12/2010 02:35:16] Chapter Content Profiter au Mieux de la Communauté Le Support Corrections et Nouvelles Fonctionnalités Plugins Conférences et Événements Réputation Participer à la Communauté Le Forum et les Listes de Diffusion IRC Contribuer au Code Documenter Présentations Organiser un Evénement Devenir Actif Localement Intégrer la Core Team The More with symfony book | Travailler avec la Communauté symfony | symfony | Web PHP Framework symfony-docs pour les discussions concernant la documentation de symfony ; symfony-community pour les sujets traitant des initiatives de la communauté. Gardez bien à l'esprit que la liste de diffusion est un mode de communication indirect et bien moins rapide que ne peut l'être IRC par exemple. Par conséquent, vous devrez attendre quelques heures parfois plusieurs jours avant d'obtenir une réponse appropriée. Il est donc important d'être réactif aux questions que pourrait susciter la vôtre. De manière générale, il convient de rester patient en permanence. Contrairement à IRC, vous devrez accompagner votre demande d'un maximum d'informations. Indiquez votre configuration, l'ORM que vous utilisez, votre système d'exploitation, les solutions que vous avez essayées et ce qui n'a pas fonctionné. N'hésitez pas à ajouter du code d'exemple car plus vous expliciterez le contexte, et les réponses seront pertinentes. IRC Par nature, IRC est la façon la plus rapide d'obtenir une réponse. Symfony possède son propre canal - #symfony - sur le réseau Freenode. Bien qu'une centaine de personnes puissent être connectées simultanément, il n'en demeure pas moins qu'elles sont certainement au travail. Par conséquent, vous devrez probablement faire face à un peu de patience avant d'obtenir une réponse de leur part. IRC se prête mal à l'affichage de gros blocs de code. Si le cas se présente durant une discussion, des services comme pastebin.com vous permettent de formater votre code sur une page web. Vous pouvez ainsi communiquer l'URL de cette page sur IRC. En réalité, poster un bloc de code sur IRC revient généralement à s'attirer les foudres des autres participants, ce qui ne jouera pas en votre faveur. Une fois votre question posée sur le canal IRC, prêtez attention à toutes les réponses que vous obtenez. Soyez réactif à d'éventuelles questions complémentaires. Certaines personnes remettront votre approche du problème en question. Parfois elles auront raison, et d'autres fois elles auront une vision erronée de votre problème. Dans tous les cas, répondez à toutes les questions que l'on pourrait vous poser afin d'aider les gens à se faire une idée de votre problématique et de son contexte. Si des personnes font de mauvaises suppositions c'est qu'ils n'ont généralement pas assez de détails. Ne vous sentez en aucun cas offensé car les participants sont là pour vous aider. En cas d'affluence, et dans un soucis de clarté, veillez à préfixer vos réponses avec le nom de votre interlocuteur. Corrections et Nouvelles Fonctionnalités L'ensemble du code de symfony est le produit de la communauté. Bien que Sensio et Fabien y aient consacré beaucoup de leur temps, leur travail n'en demeure pas moins une production communautaire. En effet, en choisissant de rendre symfony open-source, ils ont prouvé leur attachement à la communauté ! De même que les nombreux autres utilisateurs qui ont développé de nouvelles fonctionnalités ou bien corrigé des bogues. En somme lorsque vous travaillez avec symfony (et cela vaut aussi pour tous les autres projets open-source), soyez conscient que c'est grâce aux efforts de la communauté. Plugins Symfony possède un système de plugins qui facilitent l'ajout de plugins externes aux projets symfony. Ce système de plugins est construit autour du framework PEAR, ce qui fait de lui un outil très flexible de-facto. En plus des plugins internes au framework, il existe un certain nombre d'autres greffons développés et maintenus par la communauté. Ces derniers sont disponibles sur le site des plugins et classés par catégories. N'hésitez pas à effectuer des recherches parmi eux en les triant à l'aide des filtres de catégories, ORM et versions de symfony supportées. Vous pouvez également saisir des mots clefs pour réduire votre recherche. Grâce à la communauté, un grand nombre de fonctionnalités communes aux applications web actuelles sont librement disponibles. Conférences et Événements A côté de toutes ces interactions numériques, vous pouvez aussi prendre le temps de rencontrer les membres de la communauté à l'occasion de conférences et d'évènements. La plupart des conférences PHP accueillent généralement des membres de la communauté symfony, qu'ils soient spectateurs ou participants. Vous pourrez ainsi apprendre du travail de vos paires. Il existe aussi des évènements dédiés à symfony tels que le Symfony Live, le SymfonyDay et le http://www.symfony-project.org/more-with-symfony/1_4/fr/15-Working-with-the-Symfony-Community[31/12/2010 02:35:16] Par Où Commencer ? Autres Communautés Conclusion Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Search powered by google The More with symfony book | Travailler avec la Communauté symfony | symfony | Web PHP Framework SymfonyCamp. Toutes ces manifestations sont soutenues par une entreprise mais la majorité du travail est réalisée par la communauté. En participant à ce genre d'évènements, vous en apprendrez davantage sur symfony et vous nouerez des liens avec des membres reconnus de la communauté. Ils pourront peut-être vous prêter main forte plus tard en cas de coup dur. Si vous avez l'opportunité de participer à l'un de ces évènements, n'hésitez surtout pas car cela en vaut la peine. En plus des conférences « officielles », il existe un nombre croissant de groupes d'utilisateurs symfony de par le monde. Ces groupes sont généralement indépendants et ne bénéficient pas de l'aide d'une entreprise. Il s'agit en réalité de simples rassemblements d'utilisateurs. Il est très facile d'y participer puisqu'il suffit généralement de s'y rendre. Ces réunions permettent de vous constituer un réseau de contacts qui vous aideront sur des problèmes liés à symfony. Ces groupes vous permettront également de trouver du travail ou bien de recruter de nouveaux développeurs en cas de besoin. Réputation Participer à la communauté, rencontrer ses membres, communiquer, et aussi devenir un membre actif, voilà ce qui vous permettra de construire votre propre réputation. Au début, ce travail peut paraître inutile, hormis pour son propre ego, mais il peut aussi s'avérer très intéressant. Par exemple, lorsqu'il s'agit de rechercher un nouvel emploi. En effet, avec une bonne réputation, les offres seront plus nombreuses et plus intéressantes. De la même manière, si vous êtes à la recherche de développeurs, une bonne réputation vous permettra d'intéresser de nombreux candidats. I se pourrait même que votre offre intéresse quelques grands noms de la communauté. Participer à la Communauté Toutes les communautés sont fondées sur un principe d'échange. S'il n'y avait personne pour offrir quelque chose à la communauté, il n'y aurait rien à en retirer en retour non plus. De ce fait, si vous avez bénéficié de la communauté, vous pouvez aussi lui offrir quelque chose en retour. Voyons comment vous pouvez aider la communauté à se renforcer et à s'aggrandir. Le Forum et les Listes de Diffusion Comme expliqué plus haut, le forum et les listes de diffusion sont des excellents candidats pour obtenir de l'aide. Vous y trouverez des réponses à vos questions, des suggestions pour résoudre vos problèmes et des retours d'expérience sur des problématiques récurrentes. Même si vous débutez avec symfony, l'expérience que accumulerez avec le temps vous permettra de répondre à votre tour à des questions d'autres utilisateurs. Et plus vous acquerrez d'expérience, plus vous serrez en mesure de répondre à des questions complexes. Le simple fait de suggérer certaines pistes à des utilisateurs peut également les aider dans leurs propres recherches. Étant déjà abonné à ces listes, il est très facile d'aider les autres. IRC Au même titre que les listes de diffusion, si vous êtes connectés sur le canal IRC de symfony, vous pouvez de temps à autres répondre à des questions. Il est nul besoin de rester en permanence devant votre client IRC, car la plupart des participants ne le font pas. Par contre, dès qu'ils ont besoin de faire une pause dans leurs travaux, ils consultent le canal, regardent les discussions en cours, donnent des coups de pouce (ou des solutions) à des problèmes ou bien encore discutent d'autres sujets. Être présent sur le canal peut aussi permettre aux autres participants de vous contacter en spécifiant votre pseudonyme. La plupart des clients IRC vous notifieront cette prise de contact et vous pourrez y répondre. Cela vous permet d'être plus accessible au cas où des membres de la communauté se poseraient une question dont ils savent que vous pourrez y répondre. Ainsi, même en ne faisant rien, vous aiderez des personnes en vous rendant simplement disponible. Contribuer au Code La plupart des utilisateurs de symfony sont des développeurs. Par conséquent, contribuer directement au code du framework reste pour eux la façon la moins compliquée de s'investir dans la communauté. La section suivante explique comment y parvenir. Proposer des Patches Il peut bien évidement arriver que vous trouviez un bogue dans symfony. Vous pouvez aussi avoir besoin de réaliser une fonctionnalité qui n'est pas implémentée dans le framework. Comme il n'est pas recommandé de modifier sa copie du framework car cela vous poserait des http://www.symfony-project.org/more-with-symfony/1_4/fr/15-Working-with-the-Symfony-Community[31/12/2010 02:35:16] The More with symfony book | Travailler avec la Communauté symfony | symfony | Web PHP Framework problèmes à chaque nouvelle mise à jour, il vous est plutôt recommandé de contacter directement les développeurs de symfony afin de leur exposer votre problème et votre solution. Tout d'abord, vous devez modifier votre copie de symfony afin de corriger le bogue ou d'ajouter un nouveau comportement. Ensuite, vous devez générer un différentiel des fichiers modifiés. Par exemple, avec Subversion, cela se réalise grâce à cette commande : $ svn diff > my_feature_or_bug_fix.patch Vous devez utiliser cette commande à la racine de votre copie de travail pour que tous les changements soient correctement inclus dans votre patch. Enfin, rendez-vous sur le bugtracker de symfony, et après la phase d'identification, créez un nouveau ticket. Remplissez le plus de champs possible afin de faciliter la reproduction de votre bogue. Vous pouvez aussi détailler les parties de symfony qui sont affectées par vos modifications. Dans le champ "Ticket Properties", choisissez la version de symfony pour laquelle vous avez créé le patch. Quand cela est possible, sélectionnez le composant de symfony que le patch modifie. S'il y en a plusieurs, sélectionnez celui qui est le plus concerné. Veuillez aussi à préfixer le contenu du champ "Short Summary" par [PATCH], puis cochez la case indiquant qu'il y a un patch à attacher à ce ticket, le cas échéant. Contribuer aux Plugins Améliorer le framework n'est pas à la porté de tous. Mais tous les développeurs qui utilisent symfony implémentent des fonctionnalités propres à leurs projets. Certaines de ces fonctionnalités sont trop spécifiques pour intéresser d'autres développeurs, mais la plupart le peuvent. Vous savez certainement que les « bonnes pratiques » recommandent de mettre la logique de l'application dans des plugins afin de faciliter la réutilisation du code ultérieurement, pour vous et/ou pour votre entreprise. Vous pouvez alors faire le choix de rendre ces plugins open-source et de les mettre à la disposition des autres utilisateurs de symfony. Développer un plugin symfony est une tâche simple. il suffit en effet de commencer par lire la documentation concernant la création de plugins. Le site de symfony met à votre disposition un ensemble d'outils qui vous permettent de publier vos créations via le canal PEAR des plugins symfony et d'héberger vos sources sur le dépôt Subversion des plugins. Cette solution avantageuse vous permet de ne pas avoir à configurer vous même votre serveur Subversion, votre serveur PEAR et documenter l'ensemble. De plus, si vous ajoutez votre plugin au système de plugins de symfony, idevient instantanément disponible à l'ensemble de la communauté, sans plus de configuration de votre part. Mais après tout, vous êtes libre de faire comme bon vous semble. Documenter La documentation est l'un des points forts de symfony. Elle fut initialement rédigée par la Core Team mais la communauté y a aussi beaucoup contribué. Il existe également des travaux conjoints entre la communauté et la Core Team comme le tutoriel Jobeet par exemple. Dans tous les cas, une bonne documentation doit à la fois aider les nouveaux utilisateurs et servir continuellement de référence aux développeurs plus expérimentés. Les sections suivantes exposent les différentes façons de contribuer à cette abondante documentation. Rédiger des Billets de Blog Partager votre expérience et vos connaissances au sujet de symfony apporte beaucoup plus à la communauté. Particulièrement lorsque vous traitez de sujets complexes. Bien référencés, ces billets aideront d'autres développeurs à résoudre leur propres problématiques en prenant exemple sur votre expérience. Par conséquent, ne vous limitez pas à rédiger des billets présentant symfony dans ses grandes lignes. Vous pouvez partager votre expérience, les problèmes que vous avez rencontrés ou encore présenter les toutes dernières fonctionnalités de la nouvelle version de symfony. Tous ceux qui écrivent des articles sur symfony sont invités à laisser l'adresse de leur blog dans la liste des bloggeurs symfony. Tout le contenu de ces blogs est mis en avant sur la page symfony de la communauté. Pour voir figurer vos propres billets, vous devez créer un flus RSS contenant exclusivement du contenu relatif à symfony. Merci ne pas ajouter de flux autre que votre blog (pas de flux twitter par exemple). Écrire des Articles Les développeurs les plus prolifiques peuvent creuser encore plus profond. Partout dans le http://www.symfony-project.org/more-with-symfony/1_4/fr/15-Working-with-the-Symfony-Community[31/12/2010 02:35:16] The More with symfony book | Travailler avec la Communauté symfony | symfony | Web PHP Framework monde, de nombreux magazines traitant de PHP proposent de publier des articles. Ces articles doivent être idéalement plus avancés, mieux organisés et de bien meilleure qualité qu'un simple billet de blog, mais ils ont l'avantage d'être lus par plus de monde. Ces magazines ont chacun leur propre façon de gérer les publications externes. Par conséquent, reportez-vous au magazine ou sur son site web pour connaître les modalités de publication. Les magazines internet comme les groupes d'utilisateurs PHP ou symfony, les sites de développement web, etc... peuvent aussi être intéressés par vos articles. Traduire la Documentation Généralement les développeurs PHP sont à l'aise avec l'anglais. Cependant, pour beaucoup, l'anglais n'est pas leur langue natale, ce qui rend parfois difficile la lecture de la documentation technique. La communauté symfony encourage la traduction de sa documentation en donnant les droits d'écriture sur le dépôt de la documentation à tous les traducteurs et en publiant ces traductions sur le site officiel de symfony. Ces traductions sont coordonnées sur la liste de diffusion symfony docs. Si vous êtes disposés à traduire des articles, prenez le temps d'y faire un bref passage afin d'éviter les traductions en double ou ce genre d'erreurs. Ajouter du Contenu au Wiki Le wiki est la façon la plus libre de rédiger de la documentation sur n'importe quel sujet. Symfony possède son propre wiki où ses utilisateurs peuvent ajouter librement de la documentation. Nous vous encourageons à poster vos propres oeuvres éditoriales, mais il est également possible de parfaire les articles existants en les corrigeant ou en les améliorant. Il existe aussi de vieux articles dont les contenus sont aujourd'hui obsolètes. Faire le ménage dans ces articles est aussi une bonne façon de faciliter la recherche d'autres utilisateurs. Si vous souhaitez vous faire une idée du genre d'articles que peut contenir le wiki ou si vous avez besoin d'inspiration pour écrire vos propres articles, vous pouvez accéder à la page d'accueil du wiki et consulter son contenu. Présentations Écrire des articles est un bon moyen de partager votre savoir et votre expérience. De plus, par internet ils deviennent disponibles à tous et figurent dans les réponses des moteurs de recherches. Cependant, il existe d'autres façons de partager votre expérience. Vous pouvez par exemple réaliser des présentations à l'occasion d'événements. aux conférences PHP / symfony ; dans des réunions locales de développeurs ; dans votre entreprise (pour vos collègues ou vos décideurs). Suivant le lieu et l'audience, vous aurez sûrement à adapter votre présentation. Alors que les décideurs ne seront pas intéressés par les détails techniques, les participants d'une conférence sur symfony attendront plus qu'une présentation sommaire du framework. De ce fait, prenez le temps de choisir convenablement votre sujet et préparer votre présentation. Faites relire vos diapositives et si possible faites des essais devant des personnes qui pourront vous critiquer de manière constructive. Vous pouvez toujours trouver de l'aide pour votre présentation sur la liste de diffusion de la communauté symfony, où de nombreuses personnes sont déjà intervenues lors de conférences. De même, si vous ne savez pas comment réaliser votre présentation, abonnez-vous à cette liste de diffusion afin de recevoir les appels à participation de nombreuses conférences et / ou obtenir des contacts avec des groupes ou des associations locales. Organiser un Evénement En plus des présentations aux conférences déjà existantes, vous pouvez organiser vos propres événements. Peu importe votre ambition et votre cible. Vous pouvez même organiser des événements au sein d'autres événements. Prenons comme exemple le « symfony update meeting » qui s'est tenu lors de la conférence PHPNW en 2008. Tout a commencé sur twitter et sur IRC. Plusieurs utilisateurs de symfony avaient des questions sur ce que serait symfony 1.2. Le jour de la conférence, durant l'une des pauses entre deux sessions, une dizaine de personnes se sont rassemblées dans une pièce fournie par l'organisation et ont eu un exposé sur symfony 1.2. Cet événement fut court et pour peu de personnes, mais celles-ci repartirent toutes avec une bonne idée de ce que serait, à l'époque, la nouvelle version de symfony. http://www.symfony-project.org/more-with-symfony/1_4/fr/15-Working-with-the-Symfony-Community[31/12/2010 02:35:16] The More with symfony book | Travailler avec la Communauté symfony | symfony | Web PHP Framework Un autre bon exemple est l'organisation de conférences pour la communauté comme les SymfonyCamp ou le SymfonyDay Cologne. Ces deux conférences symfony ont été organisées par des entreprises utilisant symfony qui veulent collaborer avec la communauté. Toutes ces conférences ont remporté un vif succès, notamment grâce à des intervenants passionnants. Devenir Actif Localement Comme il l'a été expliqué plus haut, tout le monde n'est pas capable de comprendre la documentation ou bien les articles écris en anglais. La communication par internet peut aussi avoir ses limites. Par conséquent, vous pouvez également vous investir localement pour symfony. Les meilleurs exemples sont les associations d'utilisateurs symfony. Depuis quelques années, de nombreuses initiatives de ce genre ont vu le jour et la plupart ont déjà organisé des rassemblements, le plus souvent informels et gratuits. La liste de diffusion de la communauté symfony est un bon endroit pour trouver des groupes ou associations dans votre région ou, à défaut, créer votre propre groupe. Vous trouverez sur cette liste des membres et des organisateurs d'autres groupes qui seront capables de vous apporter leur aide pour monter votre propre association. Outre de véritables rassemblements locaux, vous pouvez aussi promouvoir symfony dans votre langue sur internet. Lancer un portail symfony est un bon exemple. C'est le cas du site Spanish symfony portal, qui informe les visiteurs des nouveaux articles en espagnol sur symfony. Ce site met aussi à la disposition des visiteurs une importante documentation en espagnol qui permet aux développeurs espagnols d'apprendre symfony, et de se tenir au courant de ses évolutions. Intégrer la Core Team La Core Team fait aussi partie de la communauté symfony. Tous les membres de la Core Team ont commencé comme simples utilisateurs du framework. Ils se sont ensuite vus intégrés la Core Team en raison de leur forte implication dans la communauté. Symfony est une méritocratie, ce qui signifie que si vous faites preuve de talent et de compétences, vous aurez peut être la chance d'intégrer cette Core Team au même titre que n'importe lequel de ses membres. Prenez l'exemple de la nomination de Bernhard Schussek. Bernhard a rejoint la Core Team après son remarquable travail sur la seconde version du framework de tests unitaires Lime et ses nombreuses propositions de patches. Par Où Commencer ? Maintenant que vous savez comment bénéficier de la communauté mais aussi comment y contribuer, vous découvrirez dans les lignes suivantes les points de départ qui vous permettront, selon vos possibilités et vos envies, de vous impliquer dans la communauté symfony. La Liste de Diffusion de la Communauté symfony La liste de diffusion de la communauté symfony offre aux membres un moyen de discuter des initiatives de la communauté et de les rejoindre. Elle leur permet également d'échanger sur tous les autres sujets ayant trait à la communauté. Si vous souhaitez rejoindre l'une de ces initiatives, répondez simplement à la discussion relative à ce projet. Si vous avez une idée qui peut servir la communauté symfony, n'hésitez pas à la soumettre sur cette liste. De la même façon, vous pouvez y poser toutes vos questions concernant la communauté ou les différentes façons d'interagir avec ses membres. La page "How to contribute to symfony" Depuis un certain temps maintenant, le wiki du site symfony possède un page spéciale intitulée How to contribute to symfony. Cette page liste de manière exhaustive les différentes façons de vous impliquer afin d'aider symfony et sa communauté, quelles que soient vos compétences. C'est bien entendu un point de passage obligatoire pour toute personnes voulant s'impliquer dans la communauté symfony. Autres Communautés Grâce au travail de nombreuses personnes, un certain nombre de projets ont vu le jour concernant symfony et ses utilisateurs. Deux projets méritent tout particulièrement de les mentionner. Symfonians Symfonians est un site web communautaire qui liste les développeurs et les entreprises qui utilisent symfony au quotidien, ainsi que leurs projets symfony respectifs. Il permet aussi aux http://www.symfony-project.org/more-with-symfony/1_4/fr/15-Working-with-the-Symfony-Community[31/12/2010 02:35:16] The More with symfony book | Travailler avec la Communauté symfony | symfony | Web PHP Framework entreprises de publier des offres d'emploi ou de stages. Vous pouvez bien sûr entrer en contact avec les autres développeurs et les entreprises, et vous disposez également d'un annuaire des applications symfony. Ce dernier est une importante galerie qui présente l'ensemble des possibilités offertes par symfony. La diversité de ces applications rend leur exploration très intéressante et donne une bonne vision de ce que l'on peut accomplir avec ce framework. Comme ce site est communautaire, vous pouvez y ouvrir un compte et créer votre profil, mais aussi celui de votre entreprise et commencer à ajouter les applications que vous avez réalisées ainsi que des offres d'emploi. Le Groupe LinkedIn symfony En tant que développeur PHP, vous avez certainement eu vent du site Internet LinkedIn sur lequel avez probablement un profil détaillé. Pour ceux qui ne connaissent pas encore LinkedIn, il s'agit d'un site sur lequel vous avez la possibilité de construire votre propre réseau social professionnel et entrer en contact avec ses membres. LinkedIn offre également la possibilité de créer des groupes de discussion, de publier des actualités et des offres d'emploi. Symfony possède son propre groupe (identification nécessaire) et en devenant membre, vous pourrez discuter de sujets relatifs à symfony, suivre les actualités du framework et aussi publier / consulter des offres d'emploi relatives à symfony. Conclusion Désormais, vous devriez avoir une bonne idée de ce que vous pouvez attendre de la communauté symfony et de ce qu'elle peut attendre de vous. Gardez bien à l'esprit que tout projet open-source repose sur la mobilisation de sa communauté. Ce soutien peut prendre un grand nombre de formes, à commencer par les réponses aux questions des débutants, la proposition de patches, en passant par le développement des plugins et la promotion du projet. Alors, qu'attendez-vous pour nous rejoindre ? « Manipuler le Cache de Configuration de symfony Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/15-Working-with-the-Symfony-Community[31/12/2010 02:35:16] The More with symfony book | Appendix B - License | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Appendix B - License You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Attribution-Share Alike 3.0 Unported License THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. 1. Definitions a. "Adaptation" means a work based upon the Work, or upon the Work and other preexisting works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License. c. "Creative Commons Compatible License" means a license that is listed at http://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. d. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. e. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. f. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. g. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that http://www.symfony-project.org/more-with-symfony/1_4/fr/A-License[31/12/2010 02:35:21] Chapter Content Attribution-Share Alike 3.0 Unported License Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris - Français) (Maîtrise de & Doctrine The More with symfony book | Appendix B - License | symfony | Web PHP Framework transmits the broadcast. h. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. i. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. j. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. k. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 2. Fair Dealing Rights Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 3. License Grant Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, d. to Distribute and Publicly Perform Adaptations. e. For the avoidance of doubt: i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether http://www.symfony-project.org/more-with-symfony/1_4/fr/A-License[31/12/2010 02:35:21] and more... Search powered by google The More with symfony book | Appendix B - License | symfony | Web PHP Framework individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. 4. Restrictions The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. b. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection http://www.symfony-project.org/more-with-symfony/1_4/fr/A-License[31/12/2010 02:35:21] The More with symfony book | Appendix B - License | symfony | Web PHP Framework appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. d. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 5. Representations, Warranties and Disclaimer UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 6. Limitation on Liability EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. Termination a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 8. Miscellaneous a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. http://www.symfony-project.org/more-with-symfony/1_4/fr/A-License[31/12/2010 02:35:21] The More with symfony book | Appendix B - License | symfony | Web PHP Framework e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. Creative Commons Notice Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License. Creative Commons may be contacted at http://creativecommons.org/. Annexe A - Exemple de Script d'Installation Personnalisé » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/A-License[31/12/2010 02:35:21] The More with symfony book | Annexe A - Code JavaScript du Widget sfWidgetFormGMapAddress | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Annexe A - Code JavaScript du Widget sfWidgetFormGMapAddress You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Le code ci-dessous correspond au JavaScript nécessaire au bon fonctionnement du widget sfWidgetFormGMapAddress : function sfGmapWidgetWidget(options){ // this global attributes this.lng = null; this.lat = null; this.address = null; this.map = null; this.geocoder = null; this.options = options; this.init(); } sfGmapWidgetWidget.prototype = new Object(); sfGmapWidgetWidget.prototype.init = function() { if(!GBrowserIsCompatible()) { return; } // retrieve dom element this.lng = jQuery("#" + this.options.longitude); this.lat = jQuery("#" + this.options.latitude); this.address = jQuery("#" + this.options.address); this.lookup = jQuery("#" + this.options.lookup); // create the google geocoder object this.geocoder = new GClientGeocoder(); // create the map this.map = new GMap2(jQuery("#" + this.options.map).get(0)); this.map.setCenter(new GLatLng(this.lat.val(), this.lng.val()), 13); this.map.setUIToDefault(); // cross reference object this.map.sfGmapWidgetWidget = this; this.geocoder.sfGmapWidgetWidget = this; this.lookup.get(0).sfGmapWidgetWidget = this; // add the default location var point = new GLatLng(this.lat.val(), this.lng.val()); var marker = new GMarker(point); this.map.setCenter(point, 15); this.map.addOverlay(marker); // bind the move action on the map GEvent.addListener(this.map, "move", function() { var center = this.getCenter(); this.sfGmapWidgetWidget.lng.val(center.lng()); this.sfGmapWidgetWidget.lat.val(center.lat()); }); // bind the click action on the map GEvent.addListener(this.map, "click", function(overlay, latlng) { if (latlng != null) { http://www.symfony-project.org/more-with-symfony/1_4/fr/A-sfWidgetFormGMapAddress[31/12/2010 02:35:24] About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Search The More with symfony book | Annexe A - Code JavaScript du Widget sfWidgetFormGMapAddress | symfony | Web PHP Framework sfGmapWidgetWidget.activeWidget = this.sfGmapWidgetWidget; this.sfGmapWidgetWidget.geocoder.getLocations( latlng, sfGmapWidgetWidget.reverseLookupCallback ); } }); // bind the click action on the lookup field this.lookup.bind('click', function(){ sfGmapWidgetWidget.activeWidget = this.sfGmapWidgetWidget; this.sfGmapWidgetWidget.geocoder.getLatLng( this.sfGmapWidgetWidget.address.val(), sfGmapWidgetWidget.lookupCallback ); return false; }) } sfGmapWidgetWidget.activeWidget = null; sfGmapWidgetWidget.lookupCallback = function(point) { // get the widget and clear the state variable var widget = sfGmapWidgetWidget.activeWidget; sfGmapWidgetWidget.activeWidget = null; if (!point) { alert("address not found"); return; } widget.map.clearOverlays(); widget.map.setCenter(point, 15); var marker = new GMarker(point); widget.map.addOverlay(marker); } sfGmapWidgetWidget.reverseLookupCallback = function(response) { // get the widget and clear the state variable var widget = sfGmapWidgetWidget.activeWidget; sfGmapWidgetWidget.activeWidget = null; widget.map.clearOverlays(); if (!response || response.Status.code != 200) { alert('no address found'); return; } // get information location and init variables var place = response.Placemark[0]; var point = new GLatLng(place.Point.coordinates[1],place.Point.coordinates[0]); var marker = new GMarker(point); // add marker and center the map widget.map.setCenter(point, 15); widget.map.addOverlay(marker); // update values widget.address.val(place.address); widget.lat.val(place.Point.coordinates[1]); widget.lng.val(place.Point.coordinates[0]); } Annexe A - Exemple de Script d'Installation Personnalisé » http://www.symfony-project.org/more-with-symfony/1_4/fr/A-sfWidgetFormGMapAddress[31/12/2010 02:35:24] powered by google The More with symfony book | Annexe A - Code JavaScript du Widget sfWidgetFormGMapAddress | symfony | Web PHP Framework Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/A-sfWidgetFormGMapAddress[31/12/2010 02:35:24] The More with symfony book | Annexe A - Exemple de Script d'Installation Personnalisé | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book Annexe A - Exemple de Script d'Installation Personnalisé You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Le code PHP ci-dessous est un script d'installation personnalisé utilisé dans le Chapitre 06 : <?php $this->logSection('install', 'default to sqlite'); $this->runTask('configure:database', sprintf("'sqlite:%s/database.db'", sfConfig::get('sf_data_dir'))); $this->logSection('install', 'create an application'); $this->runTask('generate:app', 'frontend'); $this->setConfiguration($this->createConfiguration('frontend', 'dev')); $this->logSection('install', 'publish assets'); $this->runTask('plugin:publish-assets'); if (file_exists($dir = sfConfig::get('sf_symfony_lib_dir').'/../data')) { $this->installDir($dir); } $this->logSection('install', 'create the database schema'); file_put_contents(sfConfig::get('sf_config_dir').'/doctrine/schem , <<<EOF Product: columns: name: { type: string(255), notnull: true } price: { type: decimal, notnull: true } ProductPhoto: columns: product_id: { type: integer } filename: { type: string(255) } caption: { type: string(255), notnull: true } relations: Product: alias: Product foreignType: many foreignAlias: Photos onDelete: cascade EOF ); $this->logSection('install', 'add some fixtures'); file_put_contents(sfConfig::get('sf_data_dir').'/fixtures/fixture , <<<EOF Product: product_1: name: Product Name price: 25.95 EOF ); $this->logSection('install', 'build the model'); $this->runTask('doctrine:build', '--all --and-load --noconfirmation'); $this->logSection('install', 'create a simple CRUD module'); http://www.symfony-project.org/more-with-symfony/1_4/fr/B-Custom-Installer-Example[31/12/2010 02:35:26] About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) and more... Search The More with symfony book | Annexe A - Exemple de Script d'Installation Personnalisé | symfony | Web PHP Framework $this->runTask('doctrine:generate-module', 'frontend product Product --non-verbose-templates'); $this->logSection('install', 'fix sqlite database permissions'); chmod(sfConfig::get('sf_data_dir'), 0777); chmod(sfConfig::get('sf_data_dir').'/database.db', 0777); powered by google « Appendix B - License Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/B-Custom-Installer-Example[31/12/2010 02:35:26] The More with symfony book | A propos des Auteurs | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book A propos des Auteurs You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Fabien Potencier Fabien Potencier a découvert le web en 1994, au temps où la connexion à Internet était synonyme de bruits stridents du modem. Développeur passioné, il a immédiatement commencé à créer des sites web avec Perl. Avec l'arrivée de PHP5, il a décidé de se concentrer sur PHP et a créé le projet du framework symfony en 2004 afin d'aider son entreprise à tirer profit de la puissance de PHP pour ses clients. Fabien est un serial-entrepreneur qui a créé en autres, Sensio, une entreprise de services et de consulting spécialisée dans les technologies du web et marketing sur Internet, en 1998. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Fabien est également le créateur de plusieurs autres projets opensource, un écrivain, un bloggeur, un maître de conférences international, et un père comblé de deux enfants. Son site Web : http://fabien.potencier.org/ Sur Twitter : http://twitter.com/fabpot Jonathan H. Wage Jonathan Wage a travaillé sur une multitude de technologies web depuis près de 10 ans. Il a été à la fois le développeur principal et l'architecte d'applications chez CentreSource. Aujourd'hui, vous le trouverez chez Sensio Labs, les créateurs du framework MVC symfony. Ses principales responsabilités reposent sur la formation des autres développeurs et le travail sur les projets open-source symfony et Doctrine. Il contribue au développement de symfony et est le chef du projet Doctrine. Son site Web : http://www.jwage.com/ Sur Twitter : http://twitter.com/jwage Geoffrey Bachelet Geoffrey Bachelet est un développeur autodidacte depuis le début des années 2000. Curieux des nouvelles technologies et toujours à la recherche de nouvelles choses à apprendre, il a essayé de nombreuses technologies liées au web, dont (entre autres) de nombreux frameworks tels que Zend Framework, Ruby on Rails et merb. Il a finalement rejoint Sensio Labs où il s'est développé une grande compréhension du framework PHP symfony. Il est aussi un membre fondateur de l'association française des utilisateurs de symfony, l'AFSY, et est impatient de s'investir davantage dans la communauté symfony en écrivant et en participant à des conférences. Son site Web : http://mirmodynamics.com/ Sur Twitter : http://twitter.com/ubermuda Kris Wallsmith Kris Wallsmith est un développeur web freelance de Portland dans l'Oregon. Il passe son temps dans la Silicon Forrest à diriger la création de startups, à jouer au football et à élever ses deux enfants. Kris est également maître de conférences international, membre de la Core Team de symfony et Release Manager des versions 1.3 et 1.4. Son site Web : http://kriswallsmith.net Sur Twitter : http://twitter.com/kriswallsmith Chapter Content Fabien Potencier Jonathan H. Wage Geoffrey Bachelet Kris Wallsmith Hugo Hamon Thomas Rabaix Stefan Koopmanschap Fabrice Bernhard Ryan Weaver Laurent Bonnet Hugo Hamon Hugo Hamon est un autodidacte en technologies du web depuis la fin de l'année 2000 et travaille aujourd'hui chez Sensio Labs en tant que responsable du pôle formations. Passionné par PHP, il utilise ce langage depuis plus de 8 ans et a créé le site web français de tutoriels http://www.symfony-project.org/more-with-symfony/1_4/fr/authors[31/12/2010 02:35:29] Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine The More with symfony book | A propos des Auteurs | symfony | Web PHP Framework Apprendre-PHP.com. Hugo est impliqué dans deux associations : l'AFUP en tant que membre et l'AFSY, le groupe d'utilisateurs de symfony, en tant que fondateur et Président. Il a également co-écrit le livre Mieux Programmer en PHP avec symfony 1.2 et Doctrine aux Editions Eyrolles ainsi que divers articles pour le magazine PHP Solutions. Son site Web : http://www.hugohamon.com Sur Twitter : http://twitter.com/hhamon - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris (Maîtrise de & Doctrine - Français) Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) Thomas Rabaix Thomas est un fan de symfony depuis le début. Il travaille actuellement comme travailleur indépendant avec PHP et symfony. Il est à la fois développeur, consultant et formateur, mais il est également très actif au sein de la communauté symfony. Thomas aide également les entreprises sur les sujets d'assurance qualité et les questions d'architecture sur des projets symfony stimulants. Son site Web : http://rabaix.net Stefan Koopmanschap Stefan Koopmanschap est à la fois développeur, consultant et formateur. Investi dans le projet symfony en tant qu'utilisateur et évangéliste depuis la fin de l'année 2006, il est devenu le Responsable de la Communauté du projet à la fin de l'été 2009. Dans le passé, Stefan a également participé à d'autres projets open-source tels que Zend Framework et phpBB. En dehors de son amour pour la communauté, Stefan garde un oeil attentif sur les bonnes pratiques et améliore les compétences des développeurs désireux d'apprendre davantage. Il est maître de conférences régulier et secrétaire du groupe phpBenelux. A part PHP, Stefan a un amour inconditionnel pour la musique, la lecture et par dessus tout pour sa femme Marjolein et ses deux enfants Tomas et Yara. Son site Web : http://www.leftontheweb.com/ Sur Twitter : http://twitter.com/skoop Fabrice Bernhard Fabrice Bernhard a découvert les joies du développement PHP en 1996. Son tout premier site a fort heureusement disparu à tout jamais dans les abîmes d'Internet, mais il a aussitôt attrapé le virus. Après avoir étudié les mathématiques et l'informatique à Paris et Zurick, il est devenu entrepreneur en 2007 avec Allomatch.com, un site de recensement des évènements sportifs en France. Ensuite, il a co-fondé Theodo en 2008, une entreprise de consulting orientée sur le développement, spécialisée dans les projets web de qualité et dans les solutions open-source avec une forte préférence pour symfony. Son site Web : http://www.allomatch.com/ Son blog : http://www.theodo.fr/blog Ryan Weaver Ryan Weaver est lead programmer à Iostudio, LLC à Nashville dans le Tennesee, et est un fervent supporter de symfony et d'autres technologies open-source. Il est membre du groupe Nashville OSS et co-organisateur du groupe d'utilisateurs de symfony de Nashvile. Ryan aime aussi courir, lire et voyager avec sa petite amie Leanna. En tant que bloggeur, Ryan aime partager des solutions et enseigner des méthodes utiles pour aider les autres développeurs. Il est convaincu qu'un framework et sa documentation doivent être accessibles par tout le monde. Son site Web : http://www.thatsquality.com/ Sur Twitter : http://twitter.com/weaverryan Laurent Bonnet Laurent est architecte de plate-forme web chez Microsoft qui se concentre sur les infrastructures, le développement et le design de fermes de serveurs à haute disponibilité. Son nom de code Microsoft est lorenbo, et a été créé en janvier 1992, lorsqu'il a rejoint l'entreprise en tant qu'ingénieur logiciel en assistance aux développeurs sur des noyaux Windows NT. Il rejoint le monde du développement en 1994 pour mettre en place le programme de direction régionale, où il a recruté des intégrateurs spécialisés pour représenter Microsoft lors de séminaires et de conférences. http://www.symfony-project.org/more-with-symfony/1_4/fr/authors[31/12/2010 02:35:29] and more... Search powered by google The More with symfony book | A propos des Auteurs | symfony | Web PHP Framework Puis il a sauté dans le train en marche d'Internet au début de l'année 1996 en tant que chef de projet technique de Visual Studio/Visual InterDev et Internet Explorer. En 1998 et 1999, il devient le représentant français de Microsoft à l'AFNOR (organisme représentant la France à l'ISO), où il a participé au processus de standardisation de Java. Laurent rejoint une position internationale en 2000 pour développer l'infrastructure de Microsoft Commercial Internet System, une solution de portail d'e-commerce hautement évolutive codéveloppée par CompuServ, visant Telcos et Hosters. Il rejoint Microsoft France en 2004, pour piloter une nouvelle activité consacrée aux hébergeurs et aux communautés du web. Il est toujours à ce poste 6 ans plus tard, aidant les grands hébergeurs en France à tirer parti des produits et solutions basées sur Windows. Plus récemment, il s'est intéressé aux marchés français à bas-prix. Laurent est un intervenant régulier des divers évènements internationaux (MS-Days, Tech days, Hosting Days). Son web Web : http://blogs.msdn.com/laurenbo, et occasionnellement sur http://laurenbo.wordpress.com/ Sur Twitter : http://twitter.com/laurenbo A Propos des Traducteurs » Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/authors[31/12/2010 02:35:29] The More with symfony book | A Propos des Traducteurs | symfony | Web PHP Framework About Installation Documentation Plugins Community Blog Development The More with symfony book A Propos des Traducteurs You are currently browsing "The More with symfony book" in French for the 1.4 version - Switch to language: French (fr) This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Hugo Hamon Hugo Hamon est un autodidacte passionné des technologies web depuis la fin de l' année 2000, et travaille aujourd'hui chez Sensio Labs en tant que développeur et responsable de formations. Passioné par PHP, il pratique le langage depuis 8 ans et c'est avec celui-ci qu'il a bâti le site français Apprendre-PHP.com. Hugo s'implique aussi dans deux associations françaises : l'AFUP comme membre de l'organisation et tout récemment dans l'AFSY, le groupe des utilisateurs symfony, en tant que membre fondateur et Président. Il est enfin le co-auteur de l'ouvrage Mieux Programmer en PHP avec symfony 1.2 et Doctrine publié aux Editions Eyrolles. About You are currently reading "The More with symfony book" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license. Support symfony! Buy this book or donate. Son site Web : http://www.hugohamon.com Son Twitter : http://twitter.com/hhamon Guillaume Bretou Guillaume BRETOU est un ingénieur en informatique qui a étudié à Polytech Paris Sud (ex IFIPS). Passionné du web et du PHP, il utilise ce langage depuis le début de son cursus informatique. Il a pu travailler pour divers grands comptes dont la société Arianespace pour laquelle il a réalisé plusieurs applications avec le framework Symfony. Il travaille aujourd'hui chez Sensio Labs en tant que développeur symfony. **Son twitter : http://twitter.com/gbretou* Xavier Briand Xavier baigne depuis toujours dans l'univers de l'informatique, et a rapidement choisi Internet comme terrain de jeu. Après ses études, il s'est essayé au métier de freelance et à la création d'entreprise avant de rejoindre Sensio Labs. Passionné du web et de la vie, il pratique alternativement le roller, les arts martiaux, la batterie, la photographie et passe son temps à lire (fantasy/sci-fi). Site perso : http://ezrk.net Son twitter : http://twitter.com/xavierbriand Nicolas Ricci Nicolas a attrapé le virus Symfony en 2006, et a contaminé de nombreux autres développeurs par la suite. Il travaille aujourd'hui comme consultant pour l'AgencePix. Son site Web : http://www.nrsoft.co.uk Thomas Rabaix Thomas est un fan de symfony depuis le début. Il travaille actuellement comme travailleur indépendant avec PHP et symfony. Il est à la fois développeur, consultant et formateur, mais il est également très actif au sein de la communauté symfony. Thomas aide également les entreprises sur les sujets d'assurance qualité et les questions d'architecture sur des projets symfony stimulants. Chapter Content Hugo Hamon Guillaume Bretou Xavier Briand Nicolas Ricci Thomas Rabaix Fabrice Bernhard Son site Web : http://rabaix.net Fabrice Bernhard Fabrice Bernhard a découvert les joies du développement PHP en 1996. Son tout premier site a fort heureusement disparu à tout jamais dans les abîmes d'Internet, mais il a aussitôt attrapé le virus. Après avoir étudié les mathématiques et l'informatique à Paris et Zurick, il est devenu entrepreneur en 2007 avec Allomatch.com, un site de recensement des évènements sportifs en http://www.symfony-project.org/more-with-symfony/1_4/fr/translators[31/12/2010 02:35:32] Be trained by symfony experts Jan 24: Paris (Maîtrise de & Doctrine - Français) Feb 21: Paris (Maîtrise de & Doctrine - Français) Mar 21: Paris - Français) (Maîtrise de & Doctrine The More with symfony book | A Propos des Traducteurs | symfony | Web PHP Framework France. Ensuite, il a co-fondé Theodo en 2008, une entreprise de consulting orientée sur le développement et spécialisée dans les projets web de qualité et dans les solutions open-source avec une forte préférence pour symfony. Apr 18: Paris (Maîtrise de & Doctrine - Français) May 23: Paris (Maîtrise de & Doctrine - Français) Son site Web : http://www.allomatch.com/ and more... Son blog : http://www.theodo.fr/blog Search « A propos des Auteurs powered by google Questions & Feedback If you find a typo or an error, please register and open a ticket. If you need support or have a technical question, please post to the official user mailing-list. Powered by - Make a donation - "symfony" is a trademark of Fabien Potencier. All rights reserved. Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting. Sensio Labs also supports several large OpenSource projects. Open-Source Products Services Symfony - MVC framework Symfony Components Doctrine - ORM Swift Mailer - Mailing library Twig - Template library Pirum - PEAR channel server Trainings - Be Trained by experts Guru - Get a guru for a day Partners - Specialists around the world Books - Read Open-Source books Conferences - The Symfony Live Conference > More http://www.symfony-project.org/more-with-symfony/1_4/fr/translators[31/12/2010 02:35:32]