Scroll Top

Les bonnes pratiques de développement Symfony

Symfony est un Framework Français qui permet de faciliter le développement d’une application web.
Framework signifiant “cadre de travail”, Symfony nous donne les outils pour développer rapidement, mais mal utilisés, cela peut vite devenir une usine à gaz.
Le but de cet article est de vous donner quelques clés / bonnes pratiques à mettre en place lors du développement de votre application.

Se lancer dans le développement d’une nouvelle application n’est jamais une tâche facile.
Entre conception, modélisation et production de nombreuses étapes sont à mettre en place.
Les actions à entreprendre ne se limitent pas qu’à la chefferie de projet, certaines concernent le développement.

Symfony est un Framework Français qui permet de faciliter le développement d’une application web.
Framework signifiant “cadre de travail”, Symfony nous donne les outils pour développer rapidement, mais mal utilisés, cela peut vite devenir une usine à gaz.

Le but de cet article est de vous donner quelques clés / bonnes pratiques à mettre en place lors du développement de votre application.

Nous avions déja abordés les avantages de Symfony dans un article sur les sites sur mesure via le Framework Symfony, je vous invite à le consulter si vous souhaitez en savoir plus sur ce Framework.

1 – Les bonnes pratiques, pourquoi ?

Les bonnes pratiques sont à la base de tout développement. Elles permettent de faciliter la compréhension du code, de le rendre plus lisible et donc plus facilement maintenable.
Pour ma part ces bonnes pratiques ont 3 avantages majeurs :

  • La qualité du code : elles permettent d’avoir un code “propre” et donc fonctionnel, réfléchit et structuré
  • La lisibilité du code : un code “bien écrit” permet de comprendre rapidement ce que fait une classe ou une fonction, sans avoir à scroller sur une fonction de 5000 lignes illisible ….
  • La pérennité du code : Au-delà de les appliquer au moment du développement, les bonnes pratiques permettent de retravailler sur un projet qu’on n’a pas touché de plusieurs mois/années, fait par soi ou d’autres développeurs

Au final, les bonnes pratiques s’appliquent à tout type de développement, que ce soit sur Symfony, Laravel, ou même du développement natif.

2 – Les bonnes pratiques de développement

Il est important de noter que ces bonnes pratiques sont à adapter en fonction de votre projet, de sa taille, de son envergure, de son équipe de développement, etc …

De nombreux articles existent déjà sur les bonnes pratiques Symfony, les Normes PSR ou la de modélisation de données.
Je vous laisserai le soin de les consulter car ils sont très complets et très bien écrits.

Je n’aborderai dans mon cas que les bonnes pratiques que j’applique au quotidien, et qui me permettent de développer rapidement et efficacement.

2.1 – Les conventions de nommage

Les conventions de nommage sont très importantes dans un projet. Elles permettent de savoir rapidement à quoi correspond une variable, une fonction, une classe, etc …

Je ne parlerai pas des différents cas d’utilisation du CamelCase, du snake_case, du PascalCase, etc …
mais plutôt du nom des variables que vous allez utiliser.

Dans nos exemples nous codons en anglais, mais il est tout à fait possible de coder en français (nous le faisons sur des projets très “métiers” ou l’on perd plus de temps à vérifier la traduction d’un champ qu’à coder), tant que vous restez cohérent dans votre code.

L’un des points les plus importants est de choisir le nom de votre variable. Elle peut en dire long sur ce qu’elle est quand elle est bien choisit

Ce que nous évitons

//variable temporaire
$tmp = $request->get('only_user', false);

//variable trop generique
$users = $this->em->getRepository(User::class)->findBy(['is_active' => true]);


//s'applique aussi aux nom de fonctions
function getUsers() : array
{
    return $this->em->getRepository(User::class)->findBy(['is_active' => true]);
}
Ce que nous faisons

//variable temporaire > nommez les quand même cela ne coute pas grand choses
$isOnlyUser = $request->get('only_user', false);

//essayer de donner un peu d'informations
$activeUsers = $this->em->getRepository(User::class)->findBy(['is_active' => true]);

// essayer de donner des infos moins génériques
function getAdminUserList() : array
{
    return $this->em->getRepository(User::class)->findBy(['is_active' => true]);
}

En général, nous appliquons quelques règles simples


//pluriels pour les tableaux ou les ArrayCollection
$users = $this->em->getRepository(User::class)->findAll();

//singulier pour les objets (on essaye de reprendre le nom de la classe)
$user = $this->em->getRepository(User::class)->find(1);

//utiliser is, has, countains pour des booléens
$isOnlyUser = true;
$hasChildrens = = $nav->getChildrens()->count() > 0;
$containsElements = $order->getProducts()->count() > 0;

2.2 – Les early return

Les early return sont une pratique qui consiste à sortir d’une fonction le plus tôt possible.

Cela permet de réduire la complexité de la fonction, et de ne pas imbriquer trop de conditions

Ce que nous évitons

/* UserManager.php */

public function activateUser(User $user) : void
{
    if (!$user->getIsActive()) {

        $isOnBlackList = false;

        foreach ($this->blackListedUsers as $blackListedUser) {
            if ($blackListedUser->getId() === $user->getId()) {
                $isOnBlackList = true;
            }
        }

        if(!$isOnBlackList){
            $user->setIsActive(true);
            $user->removeRole('ROLE_INACTIVE');
            $this->em->flush();
        }
    }

}

// exemple générique

if (condition1){
    if (condition2) {
        if (condition3) {
            // -- instructions
        }
    }
}
Ce que nous faisons

public function activateUser(User $user) : void
{
    if ($user->getIsActive()) {
        return;
    }

    //on en profite pour refactoriser pour un peu plus de lisibilité
    if($this->isUserBlackListed($user)){
        return;
    }

    $user->setIsActive(true);
    $user->removeRole('ROLE_INACTIVE');
    $this->em->flush();

}

// exemple générique (on peut refactoriser plus mais c'est pour l'exemple)

if (!condition1){
   return;
}

if (!condition2){
   return;
}

if (!condition3){
   return;
}

// -- instructions
        

2.3 – Avoir des controllers lisibles

Un controller Symfony est censé être le plus léger possible. Son rôle est de récupérer les informations de la requête, et de rendre une réponse.

Tout ce qui se passe entre ces deux étapes doit être reporté dans une (ou plusieurs) classe, un service, ou dans un repository avec un nom clair.

Ce que nous évitons

/* UserController.php */

#[Route('/')]
public function indexAction(Request $request) : Response
{
    $users = $this->em->getRepository(User::class)->findBy([
        'is_active' => true,
        'is_admin' => false,
        'role' => 'ROLE_USER'
    ]);

    return $this->render('user/index.html.twig', [
        'users' => $users
    ]);
}


#[Route('/activate/{id}')]
public function activateAction(Request $request, User $user) : Response
{

    $user->setIsActive(true);
    $user->removeRole('ROLE_INACTIVE');

    $this->getDoctrine()->getManager()->flush();

    $this->addFlash('success', 'User activated');

    return $this->render('user/show.html.twig', [
        'user' => $user
    ]);
}
Ce que nous faisons

/* UserConroller.php */

#[Route('/')]
public function indexAction(Request $request) : Response
{
    $users = $this->em->getRepository(User::class)->getAdminUserList();

    return $this->render('user/index.html.twig', [
        'users' => $users
    ]);
}


#[Route('/activate/{id}')]
public function activateAction(Request $request, User $user, UserManager $userManager) : Response
{

    $userManager->activateUser($user);

    $this->addFlash('success', 'User activated');

    return $this->render('user/show.html.twig', [
        'user' => $user
    ]);
}

2.4 – UtiliseZ au maximum les outils Symfony

Les FormTypes

Les formulaires Symfony servent à rendre des formulaires html mais surtout à controller et valider les données

On les utilise facilement pour créer ou modifier en enregistrement, mais aussi pour faire des recherches, des filtres etc …

Voici ce que je voie souvent pour les recherches :

Ce que nous évitons

Cela reste relativement propre, mais les données ne sont pas contrôlées et on travaille avec des
tableaux pour les paramètres ce qui n’est pas très explicite


/* UserConroller.php */

#[Route('/')]
public function indexAction(Request $request) : Response
{

    $name = $request->get('name', null);
    $isActive = $request->get('is_active', null);

    $users = $this->em->getRepository(User::class)->search([
        'name' => $name,
        'is_active' => $isActive
    ]);

    return $this->render('user/index.html.twig', [
        'users' => $users
    ]);
}

/*************************************************************************************/
/* UserRepository.php */

public function search($params): array
    {
    $qb = $this->createQueryBuilder('u');

    if($params['name']){
        $qb->andWhere('u.name = :name')
            ->setParameter('name', $params['name']);
    }

    if($params['is_active']){
        $qb->andWhere('u.is_active = :is_active')
            ->setParameter('is_active', $params['is_active']);
    }

    return $qb->getQuery()->getResult();
}
Ce que nous faisons

Ici on a des données validées (et limitées) et on retrouve bien les différentes étapes du
controller:

  • Récupération du formulaire
  • validation du formulaire
  • Recherche

/* model/UserSearch.php */

class UserSearch{
    #[Assert\Length(max: 100)]
    private ?string $name = null;

    private ?bool $isActive = null;

    public function getName(): ?string
    {
        return $this->name;
    }

    public function isActive(): ?bool
    {
        return $this->isActive;
    }
}

/*************************************************************************************/
/* UserSearchType.php */

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    $builder
        ->add('name', TextType::class, [
            'required' => false,
            'label' => 'Nom',
        ])
        ->add('lastName', CheckboxType::class, [
            'required' => false,
            'label' => 'Actif',
        ]);
}

public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => UserSearch::class,
        ]);
    }
}

/*************************************************************************************/
/* UserRepository.php */

public function search(UserSearch $userSearch): array
    {
    $qb = $this->createQueryBuilder('u');

    if($userSearch->getName()){
        $qb->andWhere('u.name = :name')
            ->setParameter('name', $userSearch->getName());
    }

    if($userSearch->isActive()){
        $qb->andWhere('u.is_active = :is_active')
            ->setParameter('is_active', $userSearch->isActive());
    }

    return $qb->getQuery()->getResult();
}

/*************************************************************************************/
/* UserConroller.php */

#[Route('/')]
public function indexAction(
    Request $request,
    UserFormBuilder $userFormBuilder,
    UserFormHandler $userFormHandler,
    UserRepository $userRepository
    ) : Response
{
    //un service pour récupérer le formulaire
     $searchForm = $userFormBuilder->getSearchForm();
    //un service pour handle et vérifier
     $userSearch = $userFormHandler->handleSearch($request, $searchForm);

    $users = $userRepository->search($userSearch);

    $users = $this->em->getRepository(User::class)->search([
        'name' => $name,
        'is_active' => $isActive
    ]);

    return $this->render('user/index.html.twig', [
        'users' => $users
    ]);
}

 

Le Profiler

Le profiler Symfony est un outil très puissant qui permet de voir ce qui se passe dans votre application.
On l’utilise principalement pour:

  • Analyser le temps de rendu d’une page
  • Analyser le nombre de requêtes SQL dans la page

Je vous renvoie vers notre article qui explique très bien Pourquoi et comment accélérer la vitesse de son site

Les EventListener

Véritable équivalent des triggers SQL, les EventListener permettent de déclencher des actions à des moments précis.
Ils sont principalement utiles pour l’envoi de mail après un enregistrement ou des mises à jour de champs.
Je me devais d’en parler car c’est la base d’une utilisation avancée de Symfony : Events and Event Listeners

Le logger Symfony

Pour moi un des outils les plus utiles en terme de temps / efficacité.

  • Coder propre, c’est bien !
  • Tester c’est génial !
  • Mais être averti si votre site tombe et savoir pourquoi, c’est encore mieux !!!!

Le Logger Symfony Monolog s’installe très facilement et permet de logger des informations dans des fichiers, des mails, des bases de données, des services externes etc …

Personnellement, j’utilise l’envoie par mail sur un site simple, pour avoir rapidement des retours sur les erreurs.
Sur nos projets en général, on passe sur Sentry qui est un outil de monitoring très puissant mais qui est plus long à mettre en place.

2.5 – Les jeux de données / Fixtures

On change un peu de registre, il ne s’agit pas de code en soit mais de méthode de travail.

Les jeux de données sont des données fictives qui permettent de tester votre application. Les prévoir d’entrée de jeux est un gain de temps considérable.

Elles permettent à la fois:

  • d’avoir un jeu de données prêt quand quelqu’un récupère l’application sur sa machine
  • de pouvoir travailler facilement l’intégration avec des données déjà en base pour les listes, la pagination, etc…
  • de saisir des données particulières pouvant être utilisés pour des tests fonctionnels
  • créer un jeu de données de montée en charge pour voir comment réagit votre application avec des tables de 100 000 lignes (au besoin)

Vous pouvez les créer à la main, mais il existe des outils pour les générer automatiquement:

2.6 – Testez votre application

OK ! Cela parait évident. Mais il est important de le rappeler: Tester son application permet de s’assurer que tout fonctionne correctement.

Je ne peux compter le nombre de fois où j’ai fait planter un formulaire de création en validant directement sans remplir les champs. (ok, en plus j’utilisais un navigateur qui ne supportait pas l’html5 donc ça n’aidait pas).

Une des solutions que j’aime bien c’est de me challenger ou challenger mes équipes en mode: “Vas-y! là j’ai blindé tu ne me feras jamais planter ce code !!!!!”.

Bizarrement, on est plus inventif / précautionneux … quand il s’agit de faire planter le code de quelqu’un d’autre plutôt que le siens =)

De plus, quand vous testez votre application, pensez à le faire intelligemment, avec de vraies données. “Qsd” et “Aze” ne sont pas des noms / prénoms !!!!!!

Si vous ne saisissez pas de données cohérentes, vous ne vous rendrez pas forcément compte des erreurs d’enregistrements ou de mauvais champs (pour ma part je confonds toujours firstname et lastname =)

Enfin, comme on peut le voir partout, Les tests fonctionnels sont de très bons outils pour assurer la pérennité de votre application. Je les préfère personnellement aux tests unitaires qui sont plus indigestes et moins orientés client.

Il va sans dire que les tests fonctionnels ont un coût, et que malheureusement, ils ne sont pas toujours rentables sur des projets de petite taille.

2.7 – Refactorisez au bon moment !

Celui là est un peu plus subjectif, mais il est important de savoir quand refactoriser son code.

Quand on fait de la qualité de code, ou des tests fonctionnels, on aime bien imaginer tous les cas ! Faire les fonctions le plus générique possible etc… C’est Top ! mais cela prend du temps.
Et surtout j’ai trop entendu des “oui mais si jamais après on rajoute ça, au moins c’est prévu ….” et au final on n’a jamais rajouté ça.

Une des règles que j’ai bien mettre en place c’est “on refactorise la deuxième fois“, quand on fait un copier / coller et qu’on sait, au fond de nous qu’il ne fallait pas =)

 

Je suis conscient qu’il y a beaucoup de choses à dire sur les bonnes pratiques de développement, et que je n’ai pas tout abordé.

Mais comme je l’ai dit en introduction, je voulais vous donner mes clés pour bien démarrer un projet, celles que je voie peu dans les articles de bonnes pratiques, qui sont à la fois orientées code, gestion et logique.

J’espère que cet article vous aura plu, et qu’il vous aura donné envie de vous perfectionner dans le développement php et Symfony.

 

vous avez besoin d’un expert en développement symfony ?
Contactez-moi !
Patrice Torti, Directeur technique adjoint chez SFI