Symfony2: 30 astuces et bonnespratiques
Noel GUILBERT
• Développeur Symfony depuis 2005
• Consultant et formateur à SensioLabs
• http://www.noelguilbert.com
• http://twitter.com/noelguilbert
Avant propos
• La version actuelle est en cours de développement
• Une version stable est prévue pour bientôt
• Les éléments présenté durant cette session peuvent changer
Rappel
Les bonnes pratiques que vous avez appris avec symfony 1 sont bienévidemment toujours d'actualité:
• Pas de logique métier dans les templates, ni dans les contrôleurs: uniquement dans les classes métier
• Gérer la présentation dans les vues : pas dans les contrôleurs niles classes métier
• Ne dupliquez pas de code
• Ne modifiez pas directement les classes du framework
• ...
Here we Go !30 astuces et bonnes pratiques à propos de Symfony2
• Par où commencer ?
• Comment organiser votre code ?
• Quel moteur de templates choisir ?
• Configuration
• Gestion des erreurs
• Templating
• Comment utiliser l'injecteur de dépendances ?
• Profiling
• Debugging
• Sécurité
• Performances
1) Par où commencer?
Par où commencer ?• Documentation: http://docs.symfony-reloaded.org/master/
◦ Quick Tour: 1h pour prendre en main le framework
◦ Flat PHP application to Symfony2: un autre tutorial pour prendre en main le framework
◦ Guides: http://docs.symfony-reloaded.org/master/guides
◦ Cookbook: http://docs.symfony-reloaded.org/master/cookbook
• Sandbox: version prépackagée d'une application Symfony2
◦ Pour le moment, c'est la seule méthode recommandée pour démarrer un projet
2) Organisez vos projets!
Organisation des projetsProjets
• Avec Symfony2, aucune structure n'est imposée
• La sandbox est une structure possible, respectant les bonnespratiques
• Si vous voulez que vos projets soit facilement repris en main pardifférents développeurs, gardez le modèle de la sandbox
Organisation des applicationsLes applications
• Avec Symfony2, vous aurez généralement besoin que d'uneseule application:
◦ Le système de sécurité permet de cloisonner efficacementchaque fonctionnalité (backend/frontend)
◦ Les classes et services sont chargés uniquement si vous lesutilisez: pas d'impact sur les performances
Multi applications
• Vous pouvez avoir besoin de plusieurs applications:
◦ Projet avec beaucoup de fonctionnalités, et avec un besoincloisonnement
◦ Séparation importante entre les différentes fonctionnalités
• Généralement, vous avez surtout besoin de créer un nouveauprojet
Organisation en multi-applicationsExemple de projet avec plusieurs applications
/application1/Application1Kernel.php...
/application2/Application2Kernel.php...
/web/application1.phpapplication2.php...
Organisation des bundlesLes bundles
• Organisez vos bundles en briques logicielles: chaque bundle est responsable d'unefonctionnalité:
◦ ForumBundle
◦ BlogBundle
◦ UserBundle
◦ ShopBundle
src/Sensio/Bundle/
UserBundle/Controller/
UserController.phpShopBundle/
Controller/OrderController.php
3. Isolez vos classes métiers
Isolez vos classes métiersProblématique
• Souvent, vous avez des classes qui sont réutilisées dans plusieurs bundles
• Ces classes peuvent même être utilisées dans des projets n'utilisant pas Symfony2
Solution
• Les bundles sont uniquement des liens entre votre code métier et le framework
• Vous pouvez donc avoir des classes en dehors de vos bundles
• C'est le choix d'architecture qui a été fait pour le framework: Bundles vs Components
Séparez vos classes métiers desbundles
Exemple de structure avec une séparation des classes métiers et desbundles
src/Sensio/Bundle/
UserBundle/OrderBundle/
Business/ <--------- Classes metiersForm/
User.phpValidator/
Order.phpEntity/ <--------- Entites Doctrine2
User.php
Séparez vos classes métiers desbundles
Grâce à l'autoloading, vous pouvez très simplement charger des classes qui ne sont pas dans un bundle.
<?php
use Symfony\Component\ClassLoader\UniversalClassLoader;
$loader = new UniversalClassLoader();$loader->registerNamespaces(array(
'Sensio' => __DIR__.'/../src',));
$loader->register();
# in your applicationnew Sensio\Business\Form\User();
Spécificité pour les entitésDoctrine2
Déclarez les mappings
Il faut indiquer à Doctrine2 où sont les entités, lorsqu'elles ne sont pas dans un bundle:
# app/config/config.ymlorm:
auto_generate_proxy_classes: %kernel.debug%mappings:
Sensio:type: annotationprefix: Sensiodir: %kernel.root_dir%/../src/Sensio/Entity
4: Utilisez votre namespace
Utilisez votre propre namespaceGérez votre propre namespace:Vos projets doivent avoir leurs propre namespaces:
• n'utilisez pas le namespace Sensio de la sandbox!
◦ Sensio
◦ YourCompany
◦ MyProject
<?php
namespace Sensio;
class Product{
/* ... */}
5: Bibliothèque externes
Bibliothèques externes• Par défaut, la sandbox est fournie avec toutes les bibliothèques externe qu'elle supporte:
◦ Symfony2
◦ Twig
◦ Doctrine
◦ SwiftMailer
◦ Assetic
• Retirez les bibliothèques que vous n'utilisez pas:
◦ Supprimez les répertoires correspondants
◦ Retirez les de la configuration de l'autoloader
◦ Désactivez les bundles associés
6: Ne divulgez plus vos mots de passes
Ne divulgez plus vos mots de passesUtilisez des variables d'environnement
• Vous pouvez définir des variables d'environnement spécifiques pour vos projets Symfony2:
◦ Les variables doivent être préfixées par SYMFONY_ _
◦ Vous pouvez ensuite utiliser ces paramètres naturellement dans vos fichiers deconfiguration
Ne divulgez plus vos mots de passesConfiguration des variables d'environnement depuis un VirtualHost
# VirtualHost configuration<VirtualHost *:80>
Servername www.domain.tld
# ...
SetEnv SYMFONY__DATABASE_USERNAME "sflive"SetEnv SYMFONY__DATABASE_PASSWORD "s3cr3t"
</VirtualHost>
# Application configuration: app/config/config.yml
doctrine:dbal:
username: %database_username%password: %database_password%
7: Configuration: le format INI
Le format INI
Généralement, n'utilisez pas le format INI pour vos projets:
• La syntaxe est limité
• Pas de validation
Le format INILe format INI peut être utile dans certains cas
Si vous souhaitez fournir une application paramétrable à des profils peu techniques, vous pouvezutiliser le format INI.
; blog.ini[parameters]blog.admin.email = [email protected] = Noël
Importez ce fichier dans la configuration de votre bundle:
<container><imports>
<import resource="blog.ini" /></imports><services id="blog.notification.email"
class="Sensio\BlogBundle\Notification\Email"><argument key="admin_email">%blog.admin.email%</argument><argument key="admin_name">%blog.admin.name%</argument>
</services></container>
8: Utilisez Twig
Utilisez Twig• Deux moteurs de templates sont disponibles par défaut avec Symfony2:
◦ PHP
◦ Twig
• Twig est le moteur de template recommandé :
◦ Protection XSS
◦ Mode sandbox
◦ Limite la logique métier dans les templates
◦ Utilisable très facilement par les intégrateurs HTML
9: Surchargez les pages d'erreurs
Surcharge des pages d'erreurs
Plusieurs solutions sont à votre disposition:
• Surcharger uniquement les templates
• Surcharger les contrôleurs
• Créer un service pour surcharger le comportement du framework
Surcharge des pages d'erreurs• Surchargez les templates:
◦ app/view/FrameworkBundle/error.html.twig
◦ app/view/FrameworkBundle/error.xml.twig
◦ app/view/FrameworkBundle/error.json.twig
<!-- app/view/FrameworkBundle/error.html.twig --><!DOCTYPE html><html>
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head><body>
<h1>Oops! An Error Occurred</h1><h2>The server returned a "{{ status_code }} {{ status_text }}".</h2><div>
Something is broken. Please e-mail us at [email] and let us knowwhat you were doing when this error occurred. We will fix it as soonas possible. Sorry for any inconvenience caused.
</div></body>
</html>
Surcharge des pages d'erreurs• Par défaut, les exceptions sont récupérées par un controller.
• Vous pouvez utiliser un autre contrôleur
# app/config/config.yml
framework:exception_controller: Sensio\HelloBundle\Controller\ErrorController
• Votre contrôleur doit implémenter une méthode showAction
<?php
namespace Sensio\HelloBundle\Controller;
class ErrorController extends ContainerAware{
public function showAction(FlattenException $exception){
// do whatever you want here
return new Response(/* ... */);}
}
Surcharge des pages d'erreurs
• Si vous voulez complètement surcharger le mécanismed'exception de symfony, vous pouvez utiliser un service
• Ce service doit écouter l'évènement core.exception
Surcharge des pages d'erreursDéclaration du service
<service id="sensio.error_handle" class="Sensio\Error\ExceptionHandler"><tag name="kernel.listener"
event="core.exception"method="handleException"priority="-128"
/></service>
Implémentation du service
<?php
namespace Sensio\Error;
class ExceptionHandler{
public function handleException(EventInterface $event){
$exception = $event->get('exception');// do whatever you want here
return new Response(/* ... */);}
}
10) Contrôleurs
ContrôleursLes contrôleurs ne doivent pas hériter de la classe Controller duFrameworkBundle.
<?php
namespace HelloBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class BadController extends Controller{
// your code here}
ContrôleursPourquoi ce n'est pas conseillé:
• Les méthodes fournies par cette classe ne sont que desraccourcis
• Surtout utile pour débuter avec le framework
• Cette dépendance n'est pas obligatoire
• L'overhead ajouté n'apporte aucun bénéfice
ContrôleursCe contrôleur est correct:
<?php# Sensio/HelloBundle/Controller/Controller.phpnamespace Sensio\HelloBundle;
class SimplestController{
/*** @extra:Route("/hello")*/public function hello(){
return new Response('Hello World');}
}
ContrôleursOui, les contrôleurs peuvent être de simples objets PHP
Avantages:
• Aucune dépendance
• Pas d'overhead
• Très facile à comprendre
Inconvénients:
• Aucun moyen d'accéder aux différentesfonctionnalités du framework: request,entity_manager, etc.
ContrôleursSolution 1: Hériter de ContainerAware
<?php
namespace HelloBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerAware
class GoodController extends ContainerAware{
public function indexAction(){
$em = $this->container->get('doctrine.orm.entity_manager');}
}
Avantages:
• Plus de dépendances vers Controller
• Les mêmes fonctionnalités sont disponibles
• Pas d'overhead
ContrôleursSolution 2: utiliser des services en tant que controlleurs
Avantages:
• Vous contrôlez vos dépendances via l'injecteur de dépendances
• Vous pouvez tester unitairement vos contrôleurs
ContrôleursDéfinition d'un contrôleur via l'injecteur de dépendances
• Définition du service
<!-- HelloBundle/Resources/controllers.xml --><service id="better_controller" class="HelloBundle\Controller\ServiceController" />
• Configuration du routing
my_route:defaults: { _controller: better_controller:index }
• Votre contrôleur est toujours un objet PHP classique
<?phpnamespace HelloBundle\Controller;
class ServiceController{
public function indexAction(){}
}
ContrôleursExemple: un contrôleur ayant accès aux services suivant:
• Le service de templating
• L'entity manager
Configuration du service:
# HelloBundle/Resources/controllers.xml<service id="product_controller" class="HelloBundle\Controller\ProductController">
<argument type="service" id="templating" /><argument type="doctrine.orm.entity_manager" />
</service>
ContrôleursImplémentation du contrôleur:
<?php
namespace HelloBundle\Controller;
class ProductController{
public function __construct(EngineInterface $templating, EntityManager $em){
$this->templating = $templating;$this->em = $em;
}
public function indexAction(){
$products = $this->em->getRepository('HelloBundle:Product')->findAll();
return $this->templating->renderResponse('HelloBundle:Product:list.html.twig',array('products' => $products)
);}
}
11: Inclusion des assets
Inclusions des assetsAssets avec Symfony2
• Avec Symfony2, il n'y a plus de gestionnaire d'assets permettant d'ajouter les styles css àutiliser
• Utilisez plutôt les blocs twig pour gérer vos assets.
Dans votre layout de base:
{# app/views/base.html.twig #}<html>
<head>{%block stylesheets %}{% endblock %}
</head><body>
{# ... #}{%block javascripts %}{% endblock %}
</body></html>
Inclusions des assetsChoisissez les assets à utiliser directement dans vos templates:
{# HelloBundle/Resources/views/Default/index.html.twig #}{% block stylesheets %}
<link href="{{ assets("bundles/hello/css/styles.css") }}" rel="stylesheet" />{% endblock %}
{% block javascripts %}<script src="{{ assets("bundles/hello/js/script.js") }}" type="text/javascript"></script>
{% endblock %}
12: Utilisez les macros Twig
Twig: Utilisez les macrosCas d'utilisation classique: des boutons html personnalisés
Le code HTML correspondant pourrait être le suivant:
<a href="#" class="button"><span><span><span>
Nice Button</span>
</span></span>
</a>
Twig: Utilisez les macrosRéalisez un macro permettant de réutiliser facilement ce codeHTML
{# HelloBundle/Resources/views/macro.html.twig #}{% macro button(url, text) %}
<a href="{{ url }}" class="button"><span>
<span><span>
{{ text }}</span>
</span></span>
</a>{% endmacro %}
Réutilisez cette macro dans vos templates:
{# HelloBundle/Resources/views/Default/index.html.twig #}
{% from "HelloBundle::macro.html.twig" import button %}
{{ button("Nice Button!") }}
13: Internationalisation
InternationalisationInternationalisez vos projets
• Parce que vous ne savez pas comment votre projet va évoluer
• Parce que l'impact en terme de performances est négligeable
• Parce que implémenter l'internationalisation après coup vous prendra beaucoup de temps
Injection de dépendance
14) Apprenez à l'utiliser
L'injecteur de dépendance est à la base du framework.
Domptez-le, et vous aurez le pouvoir de Symfony2 entre vosmains!
Apprenez à utiliser l'injecteur dedépendances
Ressources sur ce sujet:
• http://www.slideshare.net/fabpot
• http://fabien.potencier.org/article/13/introduction-to-the-symfony-service-container
• http://components.symfony-project.org/dependency-injection/
15) Injection de dépendences: règles de
base
Règles générales:
• Les classes doivent être utilisables sans le conteneur
• Seul le conteneur connait les dépendances, et pas l'inverse
• Ce n'est pas LA solution à tous vos problèmes
16) Utilisez le format XML
Définir vos servicesUtilisez le format xml pour décrire vos services:
• Un peu plus verbeux
• Mais beaucoup plus simple à appréhender et à comprendre
• Autocomplétion dans les éditeurs XML qui le supporte
17) Injecteur de dépendances:
Injecteur de dépendancesQuand l'utiliser?L'injecteur de dépendances doit être utilisés pour les objetsmétiers qui ont une portée globale:
• request
• http kernel
• entity manager
• caching driver (memcache, apc, ...)
18) Quand NE PAS l'utiliser?
Injecteur de dépendancesQuand ne pas l'utiliser ?L'injecteur de dépendances ne doit pas être utilisés dans lescas suivants:
• Retrouver une instance d'un objet métier (i.e. un produitdans une base de données)
• Pour un objet n'ayant pas une portée globle: un objet métiern'étant utilisés que dans un controleur, par exemple
19) Ne pas injecter le container
Ne pas injecter le containerIl ne faut pas injecter le container dans les objets:
• Cela crée une dépendance forte avec le conteneur alors que vous avez rarement la nécessité
• Vous ne pourrez plus utiliser vos classes sans le conteneur
<?php
namespace Sensio\Product
class Manager{
public function __construct($container){
$this->dbh = $container->get('doctrine.dbal.default_connection');}
}
Injecteur de dépendancesInjecter directement le service concerné:
<?php
namespace Sensio\Product
class Manager{
public function __construct($dbh){
$this->dbh = $dbh;}
}
21) Sécurité
SécuritéLe module de sécurité est une part très spécifique de laconfiguration de votre projet
Vous y définissez:
• Comment retrouver vos utilisateurs
• A quelles ressources il peuvent accéder
• Comment il peuvent accéder à ces resources
La bonne pratique est de définir ces informations dans un fichier spécifique
SécuritéExemple:
# app/config/security.ymlsecurity:
encoders:Symfony\Component\Security\Core\User\User: sha1
providers:main:
users:foo: { password: 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33, roles: ROLE_USER }
facebook: true
firewalls:main:
pattern: /.*http-basic: truelogout: true
access_control:- { path: /admin, role: ROLE_ADMIN }- { path: /public, role: ROLE_ANONYMOUS }- { path: /.*, role: ROLE_USER }
22) Profiling
Profiling:Surveillez vos performances:
• nombre de requêtes
• temps d'exécution
• mémoire utilisée
Profiling:Surveillez également vos redirections:
• Les actions réalisant des redirections font très souvent des dizaines de requêtes
• En environnement de développement, Symfony2 intercepte ces redirections pour vous permettred'analyser l'exécution
23) Profiling en production
Profiling:
Il arrive parfois qu'un problème survienne en production sans quevous soyez capable de comprendre le problème.
• Avec symfony 1, une des solutions est d'activer le debug letemps de comprendre ce qu'il se passe.
• Avec Symfony2, vous pouvez activer le profiler en productionsans activer l'affichage des erreurs.
Activer le profiling en production:
# app/config/config_prod.ymlframework:
profiler: { only_exceptions: true }
Profiling:Importer un dump
24) Utilisez les annotations
Utilisez les annotationsLes annotations permettent de définir des comportements, des éléments de configuration, etc...
<?php
/*** @orm:Entity*/class User{
}
Ce format de configuration est très pratique:
• Il permet de définir des comportements directement dans les classes concernés
• Tout est centralisé: vous n'avez plus à vous rappeler dans quel fichier tel ou tel comportement estdéfini
25) FrameworkExtraBundle
FrameworkExtraBundle
Le bundle FrameworkExtraBundle vous donne accès à d'avantagesd'annotations pour configurer:
• Le routing
• La ou les méthodes HTTP possible pour une règle de routing(GET, POST, ...)
• les templates à utiliser
• Le cache HTTP
• Convertir des paramètres de requêtes (en entité Doctrine, parex.)
FrameworkExtraBundleExemple de contrôleur:
<?php
class AwesomeController extends ContainerAware{
/*** @extra:Route("/awesome/controller")* @extra:Method("GET")* @extra:Template("SensioHelloBundle:Default:index.html.twig")* @extra:Cache(maxage=600)*/public function indexAction(){
}}
Performances
Optimisez vos performances grâce à Symfony2.
26) Optimisez l'autoloading
Autoloading• L'un des goulot d'étranglement classique en PHP est l'autoloading.
• Les performances exceptionnelles de Symfony2 s'expliquent en partie par des solutions visant àoptimiser le chargement des classes du framework
• Vous pouvez également profiter de ces améliorations pour vos projets, grâce aux extensions del'injecteur de dépendance.
<?php
namespace Sensio\UserBundle\DependencyInjection;
class UserExtension extends Extension{
public function load(array $config, ContainerBuilder $container){
/* ... */
$this->addClassesToCompile(array('Sensio\UserBundle\Model\User'
));}
}
AutoloadingVos classes sont "compilées" dans un fichier unique:
<?php
# app/cache/prod/classes.php/* ... */
namespace Sensio\UserBundle\Model {class User {
/* your code */}
}
Avantages:
• Plus besoin de passer par l'autoloader: vos classes sont forcéments chargées à chaque requêteHTTP
• Moins d'accès disque pour charger les classes
• Ce fichier est mis en cache par les gestionnaire d'opcode (APC, EAccelerator, XCache)
Autoloading
L'arme secrète de Symfony2 est à votre portée!
• Attention : cette fonctionnalité peut avoir l'effet inverse si elleest trop utilisée
27) Cache Warmer
Cache WarmerLa commande cache:warmup permet de pré-compiler tous les fichiers nécessaire en cache:
• Templates
• Injecteur de dépendance
• Routing
• Autoloading
$> php symfony cache:warmup
28) Doctrine: vérifier laconfiguration
Doctrine: vérifier la configuration
Une commande dédiée vous permet de vérifier la bonneconfiguration de Doctrine2 dans Symfony2:
$> php app/console doctrine:ensure-production-settings
29) Routing
Routing: Utilisez le router ApacheConfigurez Symfony pour utiliser le matcher Apache:
# app/config/config.yml
framework:router: { matcher: apache }
Exportez vos règles de routing en RewriteRules:
$> php app/console router:dump > rewrite.conf
30) Assets
AssetsUtilisez assetic pour de meilleures performances:
{# HelloBundle/Resources/views/Default/index.html.twig #}{% block stylesheets %}
{% stylesheet filter="yui_css", output="/css/main.css","@HelloBundle/Resources/public/css/reset.css""@HelloBundle/Resources/public/css/styles.css"
%}<link href="{{ asset_url }}" rel="stylesheet" />{% endstylesheet %}
{% endblock %}
{% block javascripts %}{% javascript filter="closure", output="/js/script.js",
"@HelloBundle/Resources/public/js/jquery.js""@HelloBundle/Resources/public/js/hello.js"
%}<script src="{{ asset_url }}" type="text/javascript"></script>{% endjavascript %}
{% endblock %}
Questions ?Merci de votre attention!