Temps de lecture estimé : 16 minutes

SOLID : Le guide en PHP (+ 5 exemples)

Comprendre les 5 principes SOLID et apprendre à les utiliser au quotidien en tant que développeur grâce à des exemples applicables.

SOLID en informatique, c’est quelque chose dont on entend souvent parler pour progresser en tant que dev.

Que ce soit entre développeurs ou via des formations, on retrouve souvent cet acronyme dans les slides de présentation.

Et cela depuis des années…

Si tu en parles avec un autre dev, il te dira sûrement qu’il faut faire du code SOLID !

Que SOLID, ce sont les bonnes pratiques, que c’est génial, que c’est obligatoire pour progresser.

Mais est-ce vraiment le cas ?

Dans cet article, je te propose des exemples concrets en PHP de comment SOLID peut t’aider en tant que développeur.

Je te montrerai que ce n’est pas que de la théorie et que tu peux utiliser SOLID tous les jours dans ton code pour le rendre plus qualitatif !

Connaître les principes SOLID c’est bien, mais les appliquer dans son code, c’est mieux.

Note : Utilise l’IA pour appliquer ces principes dans ton code !

Qu’est-ce que SOLID ?

Tu dois sans doute déjà connaître quelques initiales, voici les 5 principes SOLID complet.

(On me les a demandés une fois en entretien d’embauche d’ailleurs).

SOLID, c’est un acronyme pour ces 5 principes de programmation.

  1. S : Single Responsibility Principle
  2. O : Open/Closed Principle
  3. L : Liskov’s Substitution Principle
  4. I : Interface Segregation Principle
  5. D : Dependency Inversion Principle

Le but n’est pas de les connaître par cœur, mais de suffisamment les comprendre pour pouvoir les utiliser.

En informatique, ces principes sont considérés comme des bonnes pratiques pour les développeurs.

Nous sommes censés les appliquer afin de produire du code de qualité.

C’est ça SOLID.

Mais bon dans la pratique, je vois très peu de personnes appliquer ces principes en entreprise ! 🙁

Pourquoi faire du code SOLID ?

SOLID est un ensemble de (seulement) 5 bonnes pratiques dont le but est de rendre le code :

  • Code moins bogué
  • Code plus lisible
  • Code plus logique
  • Code maintenable
  • Code testable
  • Code extensible (tu changes une partie du programme et il continue de fonctionner)

Comprendre SOLID et l’utiliser au quotidien te permettront surtout d’améliorer la qualité de ton code et de comprendre des codes plus évolués (comme celui de ton framework par exemple).

En une ligne : cela te fera devenir un meilleur développeur.

Ce sont des bonnes pratiques de développement un peu vieilles, mais toujours utilisées.

Tu as donc tout intérêt à comprendre ces principes, et mieux encore, à les appliquer dans ton quotidien de dev.

Faire du code SOLID diminuera grandement la dette technique de tes projets.

Une raison de plus de te former à son utilisation !

À lire si ça t’intéresse : Code de qualité : Comment bien coder ?

Comment utiliser SOLID dans son code (avec exemples concrets) ?

Dans cette partie, j’aimerais te montrer comment tu peux inclure du code SOLID dans ton projet sans que cela devienne compliqué.

Le plus gros frein à l'utilisation des principes de SOLID, c'est que tout le monde pense que c'est compliqué et que c'est réservé aux génies du dev.

Mais SOLID n’est pas si compliqué que ça à comprendre.

Le seul prérequis pour bien le maîtriser, c’est d’avoir un peu d’expérience en programmation.

⭐️ Je te conseille de mettre cet article en favoris car tu risques d’y revenir quelques fois afin de bien maîtriser tous les principes SOLID.

Avant de commencer, ne sois pas frustré de ne pas tout comprendre du premier coup.

Personnellement, j’ai mis des mois à assimiler ces notions.

Mais quand tu arriveras à comprendre et à utiliser les principes SOLID dans ton code, ta carrière de développeur fera un bond en avant.

* J’ai choisi le langage PHP pour illustrer les principes SOLID car c’est un langage que beaucoup de développeurs ont déjà utilisé.

S : Single Responsibility Principle (SRP)

C’est sans doute le principe SOLID le plus simple à comprendre.

Une classe ne doit avoir qu'une seule et unique responsabilité.

Une erreur que l’on retrouve beaucoup dans les projets, c’est d’avoir une classe type UserService.php avec tout et n’importe quoi dedans.

De plus, le nom UserService.php n’est pas du tout explicite, on ne sait pas ce que le fichier contient.

Double problème !

❌ Code qui ne respecte pas le principe de responsabilité unique (SRP)

Ici, UserService.php a plusieurs rôles (ou responsabilités).

  • Gérer la mise à jour d’un utilisateur
  • Gérer la session de l’utilisateur
  • Vérifier ses droits
  • Convertir l’objet d’un format vers un autre

Cela fait déjà pas mal, et encore.

Généralement quand cela commence comme ça sur les projets, on se retrouve avec des services un peu fourre-tout de plusieurs centaines de lignes.

Plusieurs méthodes qui font la même chose, le code est dupliqué de toute part, les classes deviennent de plus en plus lourdes…

À maintenir c’est très compliqué.

✅ Code PHP qui respecte le principe de responsabilité unique (SRP)

Reprenons l’exemple ci-dessus.

Plutôt que d’avoir une arborescence de telle sorte.

Services/
└── UserService.php
├── ...

Et ainsi avoir un service énorme par type de données (comme UserService, ImageService, StatsService…) qui contient beaucoup (trop) de codes.

On applique le principe SRP qui va naturellement tendre vers une arborescence plus facile à lire.

Services/
├── ...
└── User
    ├── UserAuthenticatorService.php
    ├── UserFormatterService.php
    ├── UserSessionService.php
    └── UserUpdatorService.php
    ├── ...

Et voici le code séparé par fichier.

⭐️ Pourquoi utiliser le principe de responsabilité unique (SRP) ?

  • Le code est beaucoup plus clair (une classe de 1000 lignes, ce n’est pas clair).
  • Chaque fichier a désormais un rôle qui lui est propre.
  • Tout le monde peut comprendre à quoi servent les classes dans le dossier Services/User grâce à au nommage.
  • Le projet sera beaucoup plus maintenable, plus facile et agréable à faire évoluer.
  • La conception modulaire aide les développeurs à coder plus rapidement (en se concentrant sur une seule action).

O : Open/Closed Principle

On commence à rentrer dans le vif du sujet avec le principe SOLID « ouvert / fermé ».

Les entités doivent être ouvertes à l'extension et fermées à la modification.

Cela signifie que l’on doit toujours favoriser l’extension du code à sa modification : on ne modifie pas le fonctionnement suivant l’entité à utiliser, on définit une fonction commune.

Souviens-toi de ceci :

Si tu commences à utiliser des instanceof avec des if ou des switch case en fonction d’un type, c’est sans doute que tu es tombé dans le piège.

❌ Code qui ne respecte pas le principe ouvert/fermé (OC)

La plupart des exemples du principe d’ouverture / fermeture que tu trouves sur internet te parlent d’objet, d’entité.

Mais cela s’applique également aux services, modules, fonctions, classes…

Ici je choisis d’afficher un message de bienvenue à mon utilisateur, et peu importe son type, mon action ne doit pas être modifiée pour chaque élément, même si son affichage est différent.

Malheureusement dans ce code, mon action displayWelcomeMessage() se devra de changer au fur et à mesure que j’ajouterai des types d’utilisateurs…

Si je veux rajouter un nouveau type d’utilisateur, disons un affilié, je vais devoir (encore) modifier le service qui fait l’action.

C’est contraire au principe ouvert / fermé.

✅ Code qui respecte le principe ouvert/fermé (OC)

Voici comment nous avons rendu notre code conforme.

  • Ouvert pour l’extension : c’est le cas ci-dessous, on peut étendre le comportement d’une méthode (ici theName()), chaque classe qui l’implémente fait ce qu’elle veut dans sa méthode.
  • Fermé à la modification : on ne change pas le code source de l’action en fonction du paramètre reçu (que j’ai un client, un utilisateur, un affilié, un administrateur ou que sais-je, le code de l’action ne sera pas modifié).

Le choix d’une interface m’a permis de déterminer une action dans le service.

Désormais je n’ai plus à modifier le comportement de mon service à chaque ajout d’entité.

L’interface m’a permis de créer un contrat entre le service et l’objet qui est affiché.

En une ligne :

« Tu veux que je t’affiche ? Pas de problème, implémente juste l’interface NameableInterface ! »

⭐️ Pourquoi utiliser le principe ouvert/fermé (OC) ?

  • Un code beaucoup plus lisible et plus clair.
  • On aura beaucoup moins de bugs ou de comportements bizarres.
  • En termes de maintenabilité, c’est génial, car l’interface et son contrat te permettent de savoir où tu vas.

L : Liskov’s Substitution Principle (LSP)

Le principe SOLID de substitution de Barbara Liskov.

Les objets dans un programme doivent être remplaçables par des instances de leur sous-type sans pour autant altérer le bon fonctionnement du programme.

Là, c’est le moment où tu es tenté de quitter ce site à tout jamais.

Apprendre SOLID c’est galère, ce principe, c’est le plus difficile pour moi.

L’idée du principe est que les enfants ne peuvent pas faire plus ou moins que leur parent.

Voici les 4 conditions que tu dois remplir pour être conforme au Liskov’s Substitution Principle.

  • La signature des fonctions (paramètres et retour) doit être identique entre l’enfant et le parent.
  • Les paramètres de la fonction de l’enfant ne peuvent pas être plus nombreux que ceux du parent.
  • Le retour de la fonction doit retourner le même type que le parent.
  • Les exceptions retournées doivent être les mêmes.

C’est assez théorique, mais tout est là.

SOLID : Principe de substitution de Liskov en PHP
En pratique, lorsque l’on utilise une classe abstraite, il est rarement possible de ne pas respecter les principes 1, 2 et 3 car ton langage t’y « obligera ».

En PHP, les exceptions ne peuvent pas être associées à une méthode (contrairement en Java avec throws par exemple), mais on s’égare là !

❌ Code qui ne respecte pas le principe de substitution de Liskov (LSP)

Un exemple parlant est de créer un système pour récupérer des articles en base de données suivant le CMS (comme WordPress, Joomla, MediaWiki…).

Plusieurs choses ne vont pas dans ce code :

  • Pas les mêmes retours de fonction.
  • Des exceptions différentes suivant les enfants.

Heureusement les paramètres en entrée ne changent pas…

✅ Code qui respecte le principe de substitution de Liskov (LSP)

Pour rappel, un enfant (un objet) ne peut pas faire plus ou moins que son parent (une interface ou une classe parente, abstraite ou non).

J’ai déclaré une classe abstraite parente à étendre pour être certain que chacun des enfants me retourne bien une liste d’articles avec une limite en entrée.

Partout dans mon code, je peux donc interchanger WordPress et Joomla, sans que ça ne casse rien.

Ces 2 classes respectent la définition du parent, elles ne font ni plus, ni moins.

On sait à quoi s’attendre en les utilisant.

Voici un autre exemple du principe de substitution de Liskov avec PHP.

Ici, on ne peut pas typer le service enfant (CarManager) avec l’enfant (Car) alors que pourtant, nous pourrions…

PHP nous oblige à respecter notre contrat avec la classe abstraite et d’utiliser un Vehicle !

La méthode sell(Vehicle $vehicle) ne peut donc pas être surchargée dans l’enfant (CarManager) avec un type différent que Vehicle.

Quand bien même nous respecterions le principe de substitution de Liskov.

C’est une limitation de PHP.

En revanche, il est tout à fait possible de substituer les objets entre eux dans un appel de fonction (comme l’appel order()).

Merci à @Nathan_Vss pour le ping !

⭐️ Pourquoi utiliser le principe de substitution de Liskov (LSP) ?

  • Ça évite les bogues où un enfant fait (surtout) plus qu’un parent (plus de paramètres, de retours, d’exceptions levées…).
  • Les mises à jour de code seront plus faciles à organiser (chaque changement du parent induit des changements vers les enfants = sécurité).
  • On gagne niveau lisibilité et qualité, le code est beaucoup plus simple à lire et à (ré-)utiliser.

I : Interface Segregation Principle (ISP)

Voici ce que dit le principe SOLID de ségrégation de l’interface.

Aucun client ne devrait être forcé d'implémenter des méthodes / fonctions qu'il n'utilise pas.

En résumé…

Il vaut mieux faire plusieurs petites interfaces qu’une seule grande.

❌ Code qui ne respecte pas le principe de ségrégation des interfaces (ISP)

Imaginons que nous ayons besoin d’afficher des informations dans l’administration, comme des utilisateurs et des commandes.

Les utilisateurs peuvent être mis à jour (le nom, le prénom…), mais pas les commandes !

Une fois que la commande est passée, on ne peut plus la modifier (normal).

Nous avons donc 2 besoins.

  • Afficher les informations de l’objet requêté en front.
  • Envoyer la mise à jour vers le back d’un de nos deux objets.

Nous avons 2 besoins distincts, mais la même interface est utilisée.

serializeToApi() est incluse dans la classe Order même si celle-ci n’en a pas besoin.

Du coup, chaque entité du projet qui implémente EntityInterface devra implémenter la méthode pour être postée via une API…

Même si ce n’est pas le cas.

✅ Code qui respecte le principe de ségrégation des interfaces (ISP)

Nous avons séparé nos 2 besoins en 2 interfaces séparées.

Le principe « Interface Segregation Principle » de SOLID est respecté, on ne passe de contrat qu’avec l’entité qui en a besoin.

La classe n’a pas besoin d’être postée via une API ?

Pas de problème, je n’implémenterai pas l’interface SerializableInterface.

Aussi simple que cela !

⭐️ Pourquoi utiliser le principe de ségrégation des interfaces (ISP) ?

  • Améliorer la qualité du code.
  • Le code est plus modulable, plus ré-utilisable.
  • Éviter les grosses interfaces rendra le code plus facile à lire et à comprendre.
  • On respecte aussi le principe de responsabilité unique.

D : Dependency Inversion Principle (DIP)

Le dernier principe SOLID stipule :

Une classe doit dépendre de son abstraction, pas de son implémentation.

Autrement dit, on évite de passer des objets en paramètre lorsqu’une interface est disponible.

Passer en paramètre une interface permet d’être certain que l’objet que tu manipules, peu importe son type, aura les bonnes méthodes associées.

Comme tu te poses la question je te réponds :

Non il n’y a aucun mal à passer des objets en paramètres de tes fonctions.

Ce principe s’applique surtout quand tu as une action commune à exercer pour plusieurs objets différents !

Exemple.

❌ Code qui ne respecte pas le principe d’inversion de dépendance (DIP)

Nous avons plusieurs moyens de paiement dans notre projet.

Ils ont une fonction commune qui est le paiement (la méthode pay()).

Pour pouvoir être correctement utilisés, ils doivent avoir cette méthode.

Ici, le problème est qu’aucune des classes n’a de contrat pour s’assurer que la méthode pay() existe bien dans chaque classe.

Dans la méthode goToPaymentPage(), on ne pourra jamais être certain que le paramètre $paymentChoosen a bien une méthode pay().

Si le prochain développeur qui rajoute un moyen de paiement ne nomme pas ses méthodes comme il faut, tout le code plante.

✅ Code qui respecte le principe d’inversion de dépendance (DIP)

Une interface pour rappel, c’est un contrat avec la classe qui l’implémente.

Ici on certifie au programme qu’il trouvera bien la méthode pay() dans chacune des classes.

On passe désormais en paramètre l’interface PaymentInterface.

Chaque objet (PayPal, Stripe…) peut être utilisé en paramètre donc, étant donné qu’il implémente cette interface.

Nous sommes désormais certains que chaque objet passé aura bien une méthode pay() !

En plus de ça, le code est bien plus élégant à lire dans la méthode goToPaymentPage().

De plus, avec 20 moyens de paiement, la lisibilité des paramètres aurait été ingérable.

⭐️ Pourquoi utiliser le principe d’inversion de dépendance (DIP) ?

  • Un code beaucoup plus facile à modifier, on peut ajouter des fonctionnalités sans crainte de bug.
  • Une lisibilité et une qualité de code accrue.

Voilà, c’était le dernier principe de SOLID… 🙂

Combien de développeurs utilisent SOLID au quotidien ?

En informatique, on utilise beaucoup de principes pour nous aider à écrire du code de qualité.

Le problème, c’est qu’on n’est pas tous sensibilisés à ces principes de programmation.

Qui utilise SOLID au quotidien en tant que développeur ?

Voici les réponses.

Sondage sur l'utilisation de SOLID au quotidien par les développeurs
https://twitter.com/alexsoyes/status/1369531720249319425

Très peu de développeurs l’utilisent, et je serais curieux de savoir pourquoi.

  • Est-ce par manque de temps ?
  • D’apprentissage ?
  • De besoin ?

Je suis persuadé que SOLID doit être plus utilisé parmi les développeurs !

Mais pour ça, il doit être compris, et c’est tout l’intérêt de cet article.

Montrer qu’en programmation, SOLID n’est pas si difficile que ça à utiliser dans son code.

Conclusion

J’ai écrit cet article car beaucoup de tutoriels en informatique sur SOLID montrent des exemples d’utilisation avec des objets que je n’utilise jamais.

Des animaux, des voitures, des formes géométriques…

Moi je suis développeur web, et j’ai besoin de faire de la programmation SOLID pour le web.

Pour bien comprendre un principe, il faut avoir des exemples concrets que l'on puisse appliquer dans son domaine.

Sinon c’est trop théorique et ça ne sert pas à grand-chose.

À ce titre, j’espère que tu auras trouvé cet article différent des autres 🙂

Pour moi, l’utilisation de SOLID dans ton quotidien de développeur doit être une habitude.

Ou du moins quelque chose que tu dois garder en tête.

Savoir que ça existe et comprendre comment cela fonctionne est déjà un grand pas.

Si jamais c’est toujours un peu flou de ton côté, n’hésite pas à laisser un commentaire que je vois comment je peux t’aider.

Comprendre SOLID en 10 minutes ?

C’était le but de cet article.

Réutiliser SOLID dans sa programmation au quotidien.

(Pour cela, il faut s’entrainer)

Écrire quelque chose qui soit compréhensible, facile à assimiler et surtout : applicable au quotidien.

Peut-être que tu n’es pas encore très à l’aise avec les interfaces et toute cette abstraction…

Laisse-toi du temps, ça viendra. Mets cet article en favoris ⭐️ et reviens-y plus tard.

Dans quelques semaines, il sera sans doute beaucoup plus clair.

Récapitulatif des 5 principes SOLID

En quelques mots pour résumer et finir cet article.

  • (S) Le principe de responsabilité unique te permet de t’assurer qu’une fonction ne fait qu’une seule et unique chose, mais qu’elle le fait bien.
  • (O) Le principe Ouvert/Fermé te demande de ne pas modifier le comportement d’une action en fonction d’un paramètre, mais plutôt d’étendre les capacités dudit paramètre grâce à une fonction définie en amont.
  • (L) Le principe de substitution de Liskov te permet d’interchanger les enfants d’une classe sans que cela ait d’incidence sur l’exécution du code.
  • (I) Le principe de ségrégation des interfaces te demande de séparer les actions le plus possible.
  • (D) Le principe d’inversion de dépendance préconise de passer des abstractions en paramètre (des contrats grâce aux interfaces) plutôt que les objets eux-mêmes.

Eh voilà !

Si tu as trouvé l’article cool, n’hésite pas à le partager à d’autres développeurs !

Plus de contenu 💡

Pour lire plus de contenu similaire dans le même thématique.

32 commentaires

  1. Avatar de Vincent Euloge

    Super article, hyper concret.

    Je suis dev web mais je ne m’occupe pas du backend, et autant je me rend compte que j’applique le S et le O, autant le reste j’ai du mal a voir comment les appliquer sur un appli React/Redux en Typescript, car j’essaye au maximum d’avoir une approche de programmation fonctionnel, je n’ai aucune « Class », et les interfaces que j’utilise sont la pour décrire mes types.

    Est ce que les principes SOLID sont réservé à la POO ?

    • Avatar de Alex

      Hey Vincent,

      Au départ je n’avais pas non plus de class dans ma première app React.

      Au fur et à mesure, j’en ai eu besoin pour simplifier le fonctionnement (ajout de méthodes dans les classes entités notamment).

      Je ne sais pas si c’est une bonne chose en revanche !

      Pour répondre à ta question, ce n’est pas simple.

      • L : Si jamais tu n’utilises pas l’extension et que tu préfères par exemple la composition, eh bien c’est vite réglé, ce n’est possible qu’en utilisant des classes.
      • I : Vu que tu utilises des interfaces tu peux les composer grâce à de plus petites interfaces. Par exemple dans mon projet React, j’ai une EntityInterface qui comporte name: string et id: number que j’utilise en paramètre des composants HTML de type select ou encore checkbox. Ce qui rejoint le point du dessous.
      • D : Il vaut mieux passer une interface EntityInterface qu’un objet User par exemple en paramètre, mais toi vu que tu n’utilises pas d’objet de type « Class »…

      À chaud, voici ce que je répondrais 🙂

      Mais c’est loin d’être absolu, la programmation fonctionnelle est un monde à part entière que je ne maîtrise pas totalement.

      Merci pour ton commentaire.

      Alex

  2. Avatar de amoqOffice

    Salut merci pour cet article super simple à comprendre. Pour ma part, j’ai l’impression que le principe Ouvert/Fermé est pareil que le principe Ouvert/Fermé puisqu’à la fin, la fonction de la classe principale doit recevoir en paramètre l’interface concernée. Je ne vois pas de grande différence à moins que je ne me trompe.

    • Avatar de Alex

      Hello ! Merci à toi pour ton commentaire !

      Tu parles du principe Ouvert/Fermé et du principe de substitution de Liskov ? C’est une différence d’interprétation plus qu’une différence technique. Les interfaces permettent de cibler les éléments communs, ceux qui doivent se comporter de manière assez similaire. Le principe de Liskov permet aussi de faire ça, sauf qu’ici on parle d’un parent et d’un enfant, ce qui conceptuellement est différent.

      Par exemple, la plupart du temps on utilise des interfaces quand il s’agit de DI et les classes parentes lorsqu’un enfant a les mêmes fonctionnalités que son parent (un contrôleur sur Symfony par exemple).

      Pas forcément facile à appréhender tout ça, je te conseille de relire l’article de temps en temps 🙂

      Bon été,
      Alex

  3. Avatar de Le Peps

    Super article, pour ma part je ne connaissais même pas SOLID. J’ai l’impression que O est en totale contradiction avec le principe « don’t repeat yourself » ou je me trompe ?

    • Avatar de Alex

      Hello !

      Du tout, les interfaces vont te permettre de simplifier la gestion de tes classes et de tes entités. C’est super propre comme manière de faire.

  4. Avatar de Stéphan

    Hello, merci pour l’info, j’applique ces best practices depuis des années sans savoir que c’est ça qu’on appelle SOLID #facepalm

  5. Avatar de Senzo

    Super article ! Je vais le relire assez souvent pour bien m’en imprégner.

    • Avatar de Alex so yes

      Merci beaucoup ! En effet je te conseille de le mettre en favoris, moi-même j’y reviens régulièrement 😇

  6. Avatar de Dérécusson Kévin

    Merci pour cette article, c’est le plus clair que j’ai pu lire à ce sujet et cette phrase en fin d’article :

    « J’ai écrit cet article car beaucoup de tutoriels en informatique sur SOLID montrent des exemples d’utilisation avec des objets que je n’utilise jamais. »

    Elle est si vrai ! Cela me fait penser que vous avez mis le doigt sur un problème récurrent de beaucoup de tutoriel, trop fantastique, trop métaphorique, trop théorique.

    Encore merci !

    • Avatar de Alex so yes

      Hey bonjour 👋

      J’avoue que je suis assez content de la manière dont il a été construit, d’autant plus si ça remarque ; merci beaucoup à toi pour ton commentaire !

      Les tutoriels trop théoriques c’est tout ce que je déteste 😇

  7. Avatar de John

    Super article! Ca passe bien avec des exemples! Je connaissais de nom mais n’avais jamais vraiment approfondi la chose. Le bouquin de Robert Martin « Coder proprement » en parle pas mal également.

    • Avatar de Alex so yes

      Tu as aimé « Clean code » du coup ? Tu l’as lu en français ?

      Merci pour ton message en tout cas 🙂

  8. Avatar de Laurent

    Bonjour, super article.

    Concernant le premier principe et l’exemple fournit comment faire pour gérer tout ces objets ensemble ? Il y a 4 objets à faire travailler ensemble pour gérer un utilisateur, est ce que le fameux service UserManager peut être conservé pour orchestrer ceux ci ? Un exemple d’utilisation comme pour les autres principes serait top 👍

    • Avatar de Alex so yes

      Salut Laurent,

      A priori si tu suis les principes de la clean architectures tu devrais arriver à bien séparer tout ça (voir mon article sur le DDD).

      Tes services doivent décrire des actions métiers, donc tu peux :

      * soit leur passer plusieurs objets si ils concernent la même action
      * soit encapsulé tout ça dans un nouvel objet (un agrégat racine), et c’est la méthode que je te recommande

      Tu veux bien me donner un exemple précis voir comment je peux illustrer ?

      👊

  9. Avatar de Laurent

    Merci de ta réponse 🙂

    Et bien un workflow très simple basé sur l’exemple de la gestion des utilisateurs.

    * Un user s’identifie sur l’application, on utilise `UserAuthenticatorService.php` pour l’authentifier.
    * Si celui-ci est inscrit, il faut persister l’utilisateur dans la session via `UserSessionService.php`
    * Admettons que il existe dans l’application un système de statistiques des connexions utilisateurs, il faudrait enregistrer cette connexion via un service `UserLoggerService.php` par exemple.

    Sachant que ces objets peuvent avoir des dépendances, comme `UserAuthenticatorService` qui devrait avoir une dépendance vers un système de stockage via une interface par exemple, pour récupérer l’utilisateur qui tente de se connecter.

    En utilisant un framework comme symfony par exemple, cela fait beaucoup de service a injecter dans le contrôleur pour gérer le workflow d’authentification.

    • Avatar de Alex so yes

      Dans ce cas je créerais un use-case qui lui reprendrait toutes les injections.

      Ton contrôleur lui ne serait qu’un point d’entrée pour appeler le use-case avec les bonnes informations 🙂

      Pour le use-cae c’est lui qui fera les différentes étapes dont tu parles au dessus.

      De cette manière, on garde le principe intact

  10. Avatar de Laurent

    Merci de la réponse,

    Cet use-case serait donc un objet injecté dans le contrôleur et qui prendrait en paramètres les différentes classes effectuant chaque tache.

  11. Avatar de Ulrich

    Merci beaucoup pour cet article. Je faisais tout le contraire dans mes codes, surtout au niveau des S et O. Je vais toute suite réorganiser les codes de tous les projets sur lesquels je sui entrain de travailler. Il est vrai que je vais de temps en temps y revenir, mais à la première lecture, je trouve déjà beaucoup de choses à modifier dans mon comportement de développeur web. Merci.

    • Avatar de Alex so yes

      Salut Ulrich,

      Merci à toi pour ton message, c’est cool de savoir que ça peut aider et t’aider à progresser !

      Reviens donner des nouvelles de ton avancée 👋

  12. Avatar de zak

    Hello Alex so yes 🙂

    Merci pour ton article 🙂

    Du coup pour la partie « Single Responsability Principle ». Ça veut dire qu’il faut faire « une fonction = une classe » ?

    Dans ton exemple, tu as UsersService.php qui a 5 fonctions. Ça veut dire qu’une fonction est égale à une classe dans ce résonnement.
    Ce n’est pas un peu « bizarre » ?

    • Avatar de Alex so yes

      Hey Zak !

      Pas vraiment.

      Il faut que les fonctions de ta classes soient limitées au scope… de ta classe.

      Donc pas nécessairement une fonction par classe.

      Ta classe, c’est ton périmètre, tes frontières, elle a un contexte et elle doit le garder.

      Chaque fonction de ta classe doit (en théorie) rester dans ses frontières et ne pas impacter les autres.

      Si ça t’intéresse il y a un gros article sur le DDD – Domain-Driven Design, qui pourra te donner d’autres pistes de reflexion 🙂

      Bon courage,

      Alex

  13. Avatar de Othelarian

    Bonjour,

    Rappeler les principes SOLID avec des exemples c’est vraiment une chouette idée. Par contre, il y a quelques points qui, pour moi, constituent un problème.

    1) La clarté du code ne dépend pas des principes SOLID. On peut écrire du code illisible avec les meilleurs principes, ce sont deux choses distinctes. D’où la nécessité d’avoir des normes d’écriture à part, souvent intégrées dans les linters, parce que suivre des patterns et principes n’a jamais rendu le code propre.

    2) Les gros fichiers vs la séparation en plein de petits fichiers est également un choix de norme, et ne relève pas des principes SOLID. Un des plus gros fichiers que j’ai vu en prod faisait 12 600 lignes, et était parfaitement lisible, et à l’inverse j’ai vu des codes avec pleins de petits fichiers et une arborescence très logique mais illisible pour les dévs de l’équipe parce que c’était trop éclaté et qu’il fallait changer de fichiers tout le temps, entraînant l’abandon du code. Il est plus intéressant de chercher l’équilibre 😉

    3) Le LSP, là c’est problématique, il y a plusieurs soucis.

    3) 1. Le second exemple est très compromis, PHP ne faisant pas d’erreur. Les arguments d’une méthode/fonction, selon le principe de Liskov, ne peuvent pas imposer des restrictions supplémentaires, comme le fait une classe enfant. Par conséquent la classe enfant (ie. « Car ») ne peut pas remplacer la classe « Vehicule », puisqu’elle est plus spécifique. C’est le principe de la contravariance des arguments (dans la définition d’une implémentation, on ne peut remplacer le type/classe d’un argument d’une fonction/méthode qu’avec un type parent du type initial). PHP agit donc correctement sur ce point en respectant le LSP. De la même manière, on peut considérer les appels à « order » avec « Car » et « Boat » parce que les deux classes étendent « Vehicule » ce qui permet de les mettre au même niveau via le DIP, et non le LSP (en effet le LSP rentrerait dans ce cadre en contradiction avec lui-même, mais en réalité théoriquement ça s’explique, c’est juste non trivial).

    3) 2. Concernant les points suivants :
    => La signature des fonctions doit être identique entre l’enfant et le parent
    => Le retour de la fonction doit retourner le même type que le parent
    => Les exceptions retournées doivent être les mêmes
    Je suppose qu’il s’agit d’une volonté de simplification, mais je pense que c’est allé un peu trop loin. Les règles de Liskov-Wing sont plus souples que ça :
    => La signature n’a pas à être identique en type, mais uniquement en taille (ie. même nombre d’argument, et même nombre de résultat (1 ou 0))
    => Les arguments de la fonction de l’objet enfant doivent être du même type ou d’un type parent que celui de la fonction de l’objet parent
    => Le retour et les exceptions doivent être du même type ou d’un type enfant
    Par exemple, si la classe définit en interne des sous-classes de l’exception c’est ok. De la même manière si la classe mère dit « cette méthode prend des poissons » et que dans la classe fille il est dit « cette même méthode prend désormais tout animal qui vit dans l’eau » c’est également ok selon le LSP.

    Je rejoins complètement l’aspect maintenabilité et l’évolutivité, qui sont en effet des avantages implicites des principes SOLID, et les exemples sont très pertinents et ajoute une vraie plus-value sur la compréhension des principes.

    De la même manière la différence entre SRP et ISP est bien marquée et bien exprimée, avec le bout de code ça rend vraiment bien (juste éviter le « éviter les grosses interfaces rendra le code plus facile à lire », ça c’est juste un avis personnel, pas un effet des principes).

    Bonne continuation.

    • Avatar de Alex so yes

      Hello !

      Merci pour ton message, trop cool d’avoir laissé un feedback aussi long.

      1) Quand tu parles de clarté de code tu fais référence à la partir où je dis que c’est « plus facile à lire » ?

      2) Pour l’équilibre c’est clairement quelque chose que j’ai dû revoir personnellement, parce-que j’ai pas mal changé là-dessus, sur ça d’ailleurs et sur le principe DRY en lisant beaucoup sur le DDD.

      Néanmoins je reste persuadé qu’un fichier doit s’inclure dans un groupe avec des actions précises pour qu’il ne soit pas « fourre-tout ».

      Je peux toujours changer d’avis mais… 🙂

      3) J’ai dû relire plusieurs fois et ça m’a fait du bien.

      function sell(Car $car): void // ❌ Declaration of CarManager::sell(Car $car): void must be compatible with VehicleManager::sell(Vehicle $vehicle):

      Qu’on soit d’accord, on parle de ça ?

      C’est le principe de la contravariance des arguments (dans la définition d’une implémentation, on ne peut remplacer le type/classe d’un argument d’une fonction/méthode qu’avec un type parent du type initial).

      Ici on ne parle que de PHP ou de manière générale ?

      Sur ce point j’ai un peu de mal, car j’aime l’idée qu’un enfant puisse se substituer à son parent dans la définition d’une classe fille.

      Mais je me trompe probablement sur la manière de penser la chose.

      De la même manière si la classe mère dit « cette méthode prend des poissons » et que dans la classe fille il est dit « cette même méthode prend désormais tout animal qui vit dans l’eau » c’est également ok selon le LSP.

      Super intéressant ça… Tu trouves ça ok toi ? Si je lis bien l’enfant renvoie une exception plus large que le parent, et ça me gène.

      AnimalException > AnimalVivantDansLeauException > NestPasUnPoissonException

      Je verrais plus les choses dans cet ordre qu’avec un ordre un peu… aléatoire ?

      En tout cas merci beaucoup pour ton message, ça me faitréfléchir et je reviendrais sûrement modifier l’article dans les moments à venir.

      Au plaisir,

      Alex

  14. Avatar de DarrHeLL

    Bonjour,
    Je me permet de préciser qu’il y a quand même une mauvaise compréhension du SRP. L’application que tu en fait n’est pas tout à fait celle définie par Oncle Bob.

    Le SRP ce n’est pas se résumer à une action spécifique par exemple une classe qui permet de changer le mail d’un utilisateur. Il faut raisonner par contexte, à savoir d’un coté une classe qui permet d’appliquer les règles de gestions donc une entité ou un agrégat et de l’autre une classe qui permet de gérer la persistance en base de donnée.

    Cela veut dire que sur le SRP on peut avoir un Entité User qui nous permet d’appliquer tout un tas de règles métier / gestion avec un multitude de méthode mais que l’on a aucun lien vers le contexte base de données.
    Pour être plus précis : la responsabilité unique de mon entité c’est d’agréger les données les mon utilisateurs en appliquant certaines règles, la responsabilité de mon repository c’est de manipuler ces données dans un stockage quelconque .

    Cf.  »Clean Architecture » par Robet C. MARTIN – page 57 – Principe SRP

    • Avatar de Alex so yes

      Bonjour,

      C’est vrai que mon idée sur le sujet a bien changé depuis mon article sur le DDD.

      Par contre je ne savais pas que les principes SOLID venait d’Uncle Bob !

      Merci pour ton commentaire, une petite mise à jour de l’article s’impose 🙂

      Alex

  15. Avatar de Ziane Abdelmadjid

    Cool et merci de ton article, agréable à lire.

    Je voulais juste nuancer l’explication donnée pour le DIP. Il me semble que ce principe invite à éviter d’utiliser des implémentations de bas niveau dans du code de haut niveau (metier) mais plutot d’utiliser les abstractions (classes abstraites ou interfaces) des ces implémentations. Ceci afin d’eviter le couplage, de pouvoir changer d’implementations ou la modifier sans impacter le haut niveau.
    Mais peut etre que ta definition s’entends dans le monde php plus que dans le monde java.

    Merci

    • Avatar de Alex so yes

      Salut Ziane, merci pour ton commentaire 🙂

      Tu aurais un exemple sous la main ? J’avoue que part défaut dans mon code, j’utilise beaucoup d’interfaces à droite à gauche, notions métiers ou pas !

      Au plaisir,

      Alex

  16. Avatar de Jospin

    Hello Alex,

    très bel article merci d’avoir partagé cela. Je laisse rarement les commentaires sur les blogs mais vu le travail abattu tu l’as bien mérité. Je suis développeur PHP/Laravel mais je ne connaissais pas du tout le principe SOLID, j’en ai eu vent en fouillant comment m’améliorer sur le web. Les exemples sont clairs et compréhensibles. Merci encore! Si t’as une chaîne Youtube je serai ravi de m’abonner.

👩‍💻 Réagir à cet article 👨‍💻

Merci de partager ton histoire avec la communauté !