Bonnes pratiques Symfony (recommandation officielle)

Il y a quelques jours, Fabien Potencier via Sensio, dévoilait un e-book rassemblant les bonnes pratiques et recommandations pour développer un projet Symfony. Compilé des recommandations postés par la communauté, le but de ce document est de donner un ensemble de conseils en lien avec la philosophie de Symfony. Je vais tenter de vous résumer brièvement les 31 conseils décrit en anglais dans ce post.

Introduction

Ce document commence par quelques conseils et quelques phrases ayant pour but de nous inspirer du document.

« Ce document n’est pas un tutoriel »

Ce document a été écrit dans le but d’être lu par l’intégralité des Symfony developers. Aussi bien, les experts que les néophytes.

« Ne refactorisez pas vos applications »

Dès le début du document, il est spécifiquement expliqué qu’à la suite de cette lecture, nous ne devons pas mettre à jour et refactorisez nos applications dans le but qu’elle colle parfaitement aux multiples recommandations de Sensio. On peut donc immédiatement faire le lien avec les premières idées développés dans le document : ces bonnes pratiques doivent nous permettre de nous simplifier la vie, tout comme simplifier la relecture de code. Pour appuyer mes propos, trois raisons sont avancées :

  • Your existing applications are not wrong, they just follow another set of guidelines;
  • A full codebase refactorization is prone to introduce errors in your applications;
  • The amount of work spent on this could be better dedicated to improving your tests or adding features that provide real value to the end users.

Créer un projet

Dans le chapitre 2 qui traite de comment déclarer et créer un projet Symfony, une première « Best Practice » est donnée :

_Toujours utiliser Composer. _Composer a introduit les bases du PHP moderne grâce à sa résolution des dépendances. Grâce à lui, les dépendances inscrites dans le fichier composer.json seront résolue et rapatrier dans votre projet de manière à ce que celui soit utilisable. De plus, des mises à jour facile pourront être réalisées.

De cette manière, vous pourrez respectez l’organisation structurelle d’un projet Symfony. (voir page 8).

Exemples :

  • app/cache stockera le cache ;
  • app/logs stockera les logs ;
  • app/Resources stockera les templates ainsi que les traductions globales à votre application ;
  • src/ stockera les bundles de votre applications ;
  • vendor/ stockera les dépendances de votre projet ;
  • web/ stockera les assets (css, js, images…) ainsi que le fichier de front (app.php) permettant par la suite de rediriger vos requêtes vers vos contrôleurs.
    En découle, une nouvelle recommendation sur les bundles. Sans faire ma propre analyse ce qui pourrait remettre en doute la philosophie de Symfony (ce que je ne veux pas), voici la recommendation faite :

Créer un unique bundle AppBundle pour une application. Ceci étant dû à la manière dont a été conçu Symfony. Chaque bundle doit être _stand-alone. _Ils devraient donc pouvoir vivre d’une manière autonome et indépendamment.

Je vous laisse méditer sur cette recommendation…

Configuration

Le troisième chapitre nous donne des conseils pour configurer son application. Certains éléments de configuration peuvent variées d’un système de développement à un système de production.

Comme la recommendation l’indique, vous devez donc définir la configuration relative à votre infrastructure dans le fichier app/config/parameters.yml.

A contrario, la configuration propre à votre projet qui sera statique, sera déclarer dans app/config/config.yml.

De plus, le fichier parameters.yml ne devra pas être versionné. C’est le fichier parameters.yml.dist qui devra l’être. C’est ce fichier qui vous donnera des options de base pour la configuration de votre application. (recommendation page 12).

Pour conséquence, le fichier _app/config/config.yml _fait lui même office de recommendation.

Définir la configuration relative à votre application dans app/config/config.yml
Suite à cette recommandation, il est toutefois conseillé de générer un fichier config.yml, config_dev.yml et un fichier config_prod.yml. Ces fichiers ont pour but de stocker les constantes propres à l’environnement de développement et l’environnement de production.
Nous devons considérer une valeur comme constante à partir du moment où sa valeur change très peu.

Injection de dépendances

Nous y voilà ! Dans le troisième chapitre, une recommandation est détaillé sur l’utilisation de dépendances. Je vous conseille d’aller lire cette recommandation si mon explication vous semble vague.

Cette recommandation concerne l’injection de service dans des services. Sensio conseille de les déclarer dans un fichier src/AppBundle/Resources/config/services.yml.

Je vous invite par ailleurs à lire mon article sur l’injection des dépendances et comment limiter l’utilisation du super service container.

Déclarer une logique interne

Le chapitre 4 traite de l’organisation de votre application. En fonction de vos besoins, l’organisation diffèrera. Pour les fonctions globales et non métier, Sensio recommande de les placer dans un dossier Utils ou simplement de les sortir de votre bundle pour le mettre dans un dossier séparé. À la suite de cela, il est fortement recommandé de déclarer votre classe comme un service. Une nouvelle recommandation est donné pour nous aider à les déclarer.

Le nom de votre service doit être le plus court possible, idéalement un simple mot.

Le choix du format des fichiers

Étant particulièrement attacher au format Yaml au sein de Symfony, j’imagine que ce point soulèvera des discordes. Sensio recommande sans concession l’utilisation du format Yaml au sein des applications. Les bundles développés sont partager entre deux formats : le XML e le Yaml.

Sensio a décidé de recommender ce dernier simplement car celui-ci est plus « user-friendly ». L’utilisation d’un autres format ne changerait rien au fonctionnement de votre application.

Ne pas définir de variable pour la déclaration de vos services

C’est une pratique que vous verrez beaucoup dans certains bundles et pourtant Sensio vous met en garde. Voici l’exemple donné dans le cookbook :

1
2
3
4
5
6
7
8
# app/config/services.yml
# service definition with class namespace as parameter
parameters:
slugger.class: AppBundle\Utils\Slugger
services:
slugger:
class:
"%slugger.class%"

Voici l’exemple d’un code inutile. Déclarer la classe utiliser pour un service est une erreur, puisque cette variable pourrait éventuellement être utilisée à autre endroit dans le code. De plus, et c’est la raison évoquée par Sensio, cela alourdit la création du service.

Le choix de l’ORM

Sensio conseille l’utilisation de l’ORM Doctrine au sein de Symfony. Étant utilisateur de Doctrine, son intégration dans Symfony est très avancée. En découle une recommandation qui relance l’organisation logique de nos projets. La déclaration des entités doit garder une logique à l’intérieur d’un bundle. Voir l’exemple page 18.

Déclaration du mapping

La déclaration du mapping Doctrine doit de préférence être réaliser grâce aux annotations qui sont recommandés dans le cadre de Doctrine mais également d’autres applications.

Utiliser les annotations pour déclarer le mapping des entités.
Un exemple est donnée à la page 19.

Mise en place de fixtures

Sensio conseille sans pour autant le mettre en avant, de mettre en place des fixtures. Pour les non-initiés, il s’agit de jeux de données qui seront utilisés par défaut pour lancer l’application avant même de la mettre en production.

Coding standards (fin du chapitre 4)

Il s’agit d’une partie que Sensio a presque éclipsé étrangement. Je dis étrangement puisque qu’il s’agit pour moi d’une partie importante.

Symfony est codé est respectant les standards PSR1 et PSR2. Ces standards doivent être promus au sein de la communauté des développeurs Symfony mais aussi au près des développeurs PHP. Symfony a mis en ligne un article permettant de rassembler et de mettre en avant les « Coding Standards ». Le leader de Sensio, Fabien Potencier a d’ailleurs mis un outils sur GitHub permettant d’effectuer des checks sur le respects des standards.

Contrôleurs

Quand il s’agit des contrôleurs, Symfony a pour philosophie « thin controllers and fat models ». Cela signifie que les contrôleurs doivent rester léger. Chaque méthode (action) qui seront accessibles via une route représentent une action. Le code de chacune de ces méthodes doit être « light » et appeler et coordonner les actions. Cest actions devront être dans des services.

Ce model présente un contrôleur comme utilisant des codes/parties de l’application.

Un contrôleur doit respecter quelques règles

  • Un contrôleur doit avoir un maximum de 5 variables.
  • Un contrôleur possède un maximum de 10 actions.
  • Chaque action doit contenir un maximum de 20 lignes.

    • Une méthode peut contenir plus de 20 lignes, à condition que le code utilisé ne puissent pas être factorisé.

      Un contrôleur doit hérité du contrôleur de base du FrameworkBundle de Sensio, et utiliser les annotations pour gérer les routes, le cache ainsi que la sécurité.

      L’utilisation des annotations est conseillé, mais l’utilisation du XML, YAML ou PHP est également valable. L’application doit seulement utiliser un unique format.

Ne pas utiliser le @Template

C’est une recommandation pour le moins inattendu et pourtant l’explication est particulièrement percutante.

Ne pas utiliser l’annotation @Template() pour configurer le template utiliser par le controlleur
L’explication est simple, le @Template utilise un TemplateListener qui est appelé lorsque l’évènement kernel.view est lancée.
Lors de la réalisation de ce document, un test a été effectué. L’utilisation du @Template prend 26ms avant de lancer la génération alors que pour cette même page qui aurait été généré en utilisant « $this->render(…) », le temps d’attente de 5 millisecondes.

Utiliser l’annotation ParamConverter

L’utilisation de cette annotation permet d’automatiser l’hydration d’une entité.

Utiliser le trick ParamConverter est bien quand c’est simple et pratique.

Templates

Le chapitre 6 nous détaille la génération de template. C’est sans _chichi _quel le document nous donne dans la foulée plusieurs recommendations.

  • Utiliser le moteur de template Twig
    Sensio recommande Twig pour de nombreuses raisons. En plus d’être un engine très utilisé par la communauté PHP, aussi bien par les développeurs PHP from-scratch, que par les développeurs Symfony et d’autres frameworks. La simplicité est mise en avant et la promotion de leur produit est clairement visible. Pour finir, Sensio garantit le support de Symfony jusque dans sa version 3. Vous pouvez d’ailleurs retrouver les premières transitions à effectuer pour préparer votre code à migrer vers la version 3.
  • Stocker les templates de l’application dans app/Resources/views.
    Par défaut, les développeurs ont pris l’habitude de stocker leurs templates dans le dossier Resources de chaque bundle. Ceci n’est pas une mauvaise chose mais Fabien Potencier recommande de stocker les templates globaux à votre application dans le dossier cité ci-dessus.
  • Définissez vos extensions Twig dans AppBundle/Twig et configurer le services dans app/config/services.yml.
    Les extensions Twig souffrent d’une mauvaise visibilité. Ces extensions pourtant très utile, sont parfois utiliser de manière trop systématique. Sensio présente cette partie que je trouve essentielle, comme une manière d’injecter du code métier dans ses templates. Je vous recommande de lire comment déclarer une extension Twig.

Forms

Le chapitre 7 nous donne les instructions sur l’utilisation des forms.

  • Définissez vos formulaires dans des classes PHP.
    Symfony donne la possibilité de générer des classes PHP permettant la génération automatiser des formulaires pour des entités données. Les Forms comme on les nomme, offre une intégration complète. La modification des entités est assistés. De plus, utiliser les Forms permet de déporter la gestion des formulaires des templates.
  • Ne pas déclarer de bouton pour envoyer son formulaire.
    Il s’agit d’un point étonnant, puisque cette fonctionnalité a été introduite dans Symfony 2.5. À l’heure où j’écris ces lignes, Symfony 2.6 est en cours de recette et n’est donc pas encore sortie officiellement. On peut donc lever quelques interrogations quant à l’utilité de cette fonctionnalités celle-ci ne doit pas être utilisée !Ajouter les bouttons dans les templates plutôt que dans les Forms, pourquoi ?
    Dans son explication, l’équipe de Sensio explique malgré que cette fonctionnalité reste « user-friendly » et qu’elle simplifie la mise en place de formulaire, certaines fonctionnalités restent bridés.
    Exemple, si vous ajoutez un bouton de type submit avec un label « Créer », ce formulaire ne pourra pas être réutilisé pour une page d’édition. Vous devrez alors, déclarer votre formulaire comme service pour effectuer un héritage, ce qui est déconseillé un peu plus bas.
  • Ne pas utiliser les fonctions Twig form() et _formstart().
    Une partie dédié au rendu des tempaltes a été mise en place. Elle nos apprend que l’utilisation des fonctions Twig form() et _ form_start()_ est déconseillé. Une nouvelle fois, il s’agit pour moi d’une erreur puisque ces fonctions sont disponibles depuis peu.
    Dans Symfony, il y a plusieurs moyens d’effectuer le rendu d’un formulaire. Parmi les différentes manières de faire le rendu, le meilleure moyen reste celui qui offre un maximum de flexibilité.
    L’utilisation des fonctions Twig citées ci-dessus, apporte peu d’avantage. Sensio évoque une lisibilité accrue lorsqu’on utilise les balises HTML natives. Malgré cet argument, cela représente un petit bénéfice… Très petit !

Déclarer ses forms comme des services

Les formulaires sont des classes PHP ce qui induit qu’elles peuvent être déclarées comme un service. Vous pourrez voir cette méthode dans les bundles de FriendsOfSymfony. Cette méthode n’est pas recommandé par Sensio sauf dans certains cas que je cite ci-dessous :

  • Si vous souhaitez réutiliser des formulaires existants. Mettre en place l’héritage entre deux forms permet de limiter la réécriture de l’intégralité des attributs.
  • Dans le cas où on souhaiterai embarquer des collections d’entités.
    L’utilisation de forms sous forme de services dans le cas d’un formulaire d’ajout ou d’édition n’est pas conseillé car l’utilisation d’un service charge le @container. De plus, il sera difficile de comprendre que ce service est utilisé par un contrôleur.

Attacher (binder) ses formulaires

Depuis peu, Sensio a amorcé une migration constante du code Symfony2 vers le code futur de Symfony3. Parmi la liste des modifications que l’on peut réaliser (vous pouvez toutes les retrouver dans UPGRADE-3.md), se trouve la nouvelle solution pour binder la requête envoyée par un formulaire avec le form qui a été créé. La nouvelle méthodologie est détaillé à la page 21 du Best Practices.

Voice l’extrait présenté :

1
2
3
4
5
6
7
8
9
10
11
12
13
public function newAction(Request $request)
{
// build your form
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->container->get("doctrine.orm.default_entity_manager");
$em->persist($post);
$em->flush();
return $this->redirect($this->generateUrl("admin_post_show"), array("id" => $post->getId()));
}
}

Internationalisation

Le module d’internationalisation a pour but de traduire le contenu d’une langue à l’autre, en fonction de la région ou de la langue de l’utilisateur. Plus que de données des recommandations, ce chapitre a plus été écrit pour montrer les possibilités sur cette partie qui reste toujours sensible.

Dans le chapitre 8, les rédacteurs détailles comment activer le module de translation.

Quid du format

Symfony propose une multitude de formats de traduction :

  • PHP
  • Qt
  • .po
  • .mo
  • JSON
  • CSV
  • INI
  • et bien d’autres

    Utiliser le format XLIFF pour vos fichiers de traductions.
    Cette citation est bien évidemment une recommandation de Sensio. Oui, mais pourquoi ? Une réponse brève et précise nous est donnée.
    Parmis tous les formats supportés, seul le format XLIFF et le format gettext sont supportés par les outils de traduction professionnels. Depuis que le XLIFF se base sur le XML, celui bénéficie d’une validation du contenu.
    Symfony 2.6 apporte une nouveauté permettant de « commenter » (ajouter des notes) dans vos fichiers XLIFF. Cela est une grosse nouveauté, car la génération de fichiers de traductions peu poser des soucis aux traducteurs pour comprendre le sens d’une phrase ou bien le contexte dans laquelle elle est utilisé. Bref, XLIFF c’est bien !

Où stocker nos fichiers de traductions ?

Cette question introduit une Best Pratice. Sensio recommande de stocker nos fichiers dans app/Resources/translations.

Habituellement, nous avions l’habitude de stocker nos traductions dans chacun de nos bundles. Cela n’était pas une mauvaise chose,mais les traductions sont globalement considérés comme des parties globales à nos applications, tout comme les templates globaux, c’est pour cette raison que Sensio nous recommande de les stocker dans le dossier app.

Comment traduire nos applications ?

Toujours utiliser des clés pour traduire vos contenus.
Sur ce Best, je ne donnerai pas mon avis puisque je n’ai que peu l’occasion de traduire des applications. Sensio conseille que pour traduire un mot comme « Username », vous devriez utiliser une clé telle que label.username.

Un exemple est donné à la page 35.

Securité

Le chapitre 9 du livre, concerne une partie centrale de Symfony ; la sécurité !

Symfony a été pensé pour facilement configurer et authentifier des utilisateurs qui souhaiteraient se connecter à nos applications. Cette partie est très complexe et pleine de détails, il s’aqit donc d’une ébauche. Vous pouvez retrouver plus de documentations sur la page dédié.

Déclarer ses firewalls

Les options de configurations sont à saisir dans le fichier security.yml qui se trouve dans app/config.

À mois que vous ayez deux connections différentes sur votre application (système et utilisateurs), nous recommandons de n’utiliser qu’un seule firewall d’entré avec l’option anonymous activé.
Wow wow wow ! Attends, j’arrive sur Symfony, j’ai rien compris !

Qu’est-ce qu’un firewall ?

Vous pouvez retrouver un article made in Wanadev, sur comment déclarer es mettre en place une authentification native sur Symfony. Dans cet article, il est détaillé qu’est-ce qu’un firewall.

Un seul firewall ? Pourquoi un ?

Si vous venez de lire l’article linké ce-dessus, vous avez pu remarquer que plusieurs firewalls ont été déclarés.

Dans l’article donné, les trois noms étaient dev, main et login. Malgré cela, la règle donné est respecté, seul un firewall peut-être considéré comme une porte d’entrée.

  • dev : ce firewall autorise la debug bar à s’afficher.
  • main : ce firewall est notre ENTRYPOINT. Les utilisateurs anonymes pourront se connecter comme pour respecter la règle. Toutes les routes commençant par /, utiliseront cette entré.
  • login : il s’agit d’un firewall. Ce sera notre porte d’entré dans un unique cas : si nous souhaitons nous connecter. Cela est essentiel pour avoir accès à un formulaire de connexion.
    La règle est donc respectée.

Définir un encodage

L’encodage des mots de passe est une décision phare. De multiples algorithmes existent, comme les sha (sha1, sha256, sha512, md5…).

Lorsqu’on déclare un encodage (voir comment déclarer un encodage), trois paramètres peuvent être donnée.

  • L’algorithme de crytage (par défaut : sha512) ;
  • Le nombre d’itération (par défaut : 5000) ;
  • L’encodage est base64 du mot de passe encodé (par défaut : true).
    Malgré ces valeurs par défaut, que je vous invite à modifier pour les modifier pour vos applications. Sensio recommande par ailleurs d’utiliser l’algorithme bcrypt.

    Utiliser l’encodage bcrypt pour encoder les mots de passe utilisateurs.
    Sensio donne des explications quant à l’utilisation de bcrypt. Celui-ci inclut un salage de la valeur. Cela limite les attaques et est plus résistant aux attaques de type « brute-force ». Vous trouverez plus de détails dans l’article Wikipedia ci-dessus.

Définir des autorisations

Lors d’une connexion, Symfony détecte quel firewall devra être utilisé. Par la suite, et avant même d’accéder au contrôleur, une vérification des accès est effectué. Ils sont définis dans le fichier security.yml,

Sensio recommande de :

  1. protéger nos « bords URL patterns », comprenez nos patterns globaux (exemple : /admin) ;
  2. Utiliser les annotations @Security autant que possible ;
  3. Vérifier les droits pour un utilisateur via le service security.context (depuis Symfony 2.6, le service a évolué, voir ici) ;
  4. Définir des voters pour gérer facilement la sécurité des routes ;
  5. Utiliser les ACL pour gérer les droits d’un objet et des utilisateurs.

Utiliser les annotations @Security

Sensio recommande l’utilisation de l’annotation @Security.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
// ...
/**
* Displays a form to create a new Post entity.
* @Route("/new", name="admin_post_new")
* @Security("has_role('ROLE_ADMIN')")
*/
public function newAction()
{
// ...
}

Utiliser des expressions pour complexifier les restrictions

Sensio nous explique que cette annotation permet également de générer des conditions plus complexes, comme une comparaison de deux attributs entre deux objets. Cette annotation peut nécessité l’utilisation de ParamConverter.

Vous trouverez plus d’informations dans la document :

Accéder à la sécurité dans Twig

Dans le cas où on souhaiterai comparer d’utilisateur connecté avec l’utilisateur d’un objet, vous pouvez accéder depuis Twig au user en cours.

1
2
3
{% if app.user … %}
{% endif %}

Gérer sa sécurité facilement

La gestion de la sécurité est souvent une partie sensible, et l’organisation de notre code est une partie essentielle à la réussite de la sécurisation de notre application. L’utilisation de Voters est fortement recommandé. D’autres manières d’organiser son code peut être envisagé. On peut par exemple, déporter une décision dans une méthode de notre entité.

Vous pouvez visualiser l’exemple de Sensio à la page 40.

Cette partie est très peu détaillé par rapport aux possibilités que Symfony offre. Je vous conseille de lire un peu plus la documentation sur cette partie si vous êtes confronté à une tâche de sécurisation.

Web Assets

Les assets nous permettent de gérer les ressources comme les Javascript, les CSS, fos fonts, les images… pour qu’elles soient accessibles depuis vos pages.

Vos assets doivent être stokés dans le dossier web/
De cette manière vous pourrez charger vos ressources dans vos templates de cette manière :

1
2
<link rel="stylesheet" href="{{ asset('css/bootstrap.min.css') }}" />
<script src="{{ asset('js/jquery.min.js') }}"></script>

Garder le dossier web public et tout ce qui est stocké dedans.

Utiliser Assetic

Assetic possède de multiples intérêts, comme la compilation des fichiers. Par exemple, les fichiers Less, Sass, TypeScript… Pour cette raison, le dossier web ne peut contenir de fichiers telle que des fichiers .less.

Utiliser Assetic pour compiler, combiner et minifier vos assets à moins que vous n’utilisiez GruntJS.

En apprendre plus sur Assetic

Assetic est un outils complet malgré quelques inconvénients. Vous pouvez vous documenter sur ce module grâce aux liens ci-dessous :

Mettre en place des tests

Les tests sont reconnus par la majorité des développeurs comme étant essentielles. Pourtant, seulement une minorité les mets en place.

Nous allons voir comment Sensio nous coneille d’effectuer nos tests.

Effectuer des tests unitaires

Les tests unitaires sont utilisés pour réaliser des tests fonctionnels qui vont pour leur part tester la logique de votre application. Symfony n’a pas déterminé d’outils particulier pour tester vos tests. Les outils PhpUnit et PhpSpec sont cités.

Réaliser des tests fonctionnels

Créer de bons scénarios pour vos tests fonctionnels est essentiel, et pourtant les développeurs rencontrent rapidement des soucis pour les mettre en place.

Définir un test fonctionnel pour tester si votre page a correctement été chargée.

Tenter d’utiliser des URL hardcodées plutôt que de les générer via le générateur.

Tester les fonctionnalités JavaScript

De nombreux outils existent comme Mink (une librairie de PHPUnit) et CasperJS.

Générer des jeux de données

C’est souvent un soucis pour les développeurs, générer des jeux de données.

Sensio nous conseille l’utilisation des librairies Faker et Alice.

Conclusion

Cet article est tiré du Best Pratices pour Symfony. Tout en restant simple, cet article a pour but de décortiquer les 50 pages de conseils de manière à guider des néo-développeurs Symfony.