Design pattern PHP : la responsabilité unique.

27/04/2018

Aucun commentaire

Vous est-t-il déjà arrivé d’avoir besoin de modifier une classe PHP mais sans pour autant casser la chaîne de responsabilité ? C’est là qu’intervient le principe de la responsabilité unique ou Single Responsibility.

Avant de connaître ce design pattern, je rencontrais beaucoup de bugs de régression dans mon code. En effet il est rare qu’une application reste immobile. Il est même fréquent que le code de celle-ci change constamment.

Plus votre application est importante et se complexifie, plus ce principe de responsabilité unique vous permettra de gagner en efficacité. Que ce soit sur la maintenance ou l’évolution du code vous pourrez enfin produire des interfaces web robustes.

Principe

Le principe de la responsabilité unique est basée sur le fait qu’une classe ne devrait n’avoir qu’une seule raison de changer. De plus celle-ci ne doit faire qu’une seule et unique chose (sinon cela reviendrait à avoir trop de responsabilités au sein de la même classe ce qui n’est pas du tout le but recherché).

Voici ci-dessous un exemple concret :

<?php

/**
* Created by PhpStorm.
* User: james
* Date: 26/04/18
* Time: 09:33
*/
class ProductController
{

   /**
    * @return array
    */
   public function storeInvoice()
   {
       $prix_ttc = $this->getTarif($_POST['amount']);
      
       $data = [
           'prix_ttc'          =>  $prix_ttc,
           'prix_ht'           =>  $this->getTarifHt($_POST['amount']),
           'montant_remise'    =>  $this->applyReduction($prix_ttc, '0.5'),
       ];
      
       return $data;
   }
  
   /**
    * Méthode pour calculer le tarif ttc
    * @param $price
    * @return mixed
    */
   public function getTarifTtc($price)
   {
       return $this->getTarifHt($price) * (1 + (20 / 100));
   }
  
   /**
    * Méthode pour calculer le tarif Ht
    * @param $price
    * @return mixed
    */
   public function getTarifHt($price)
   {
       return $price / (1 + (20 / 100));
   }
  
   /**
    * Méthode pour appliquer un pourcentage de remise
    * @param $price
    * @param $coeff_remise
    * @return mixed
    */
   public function applyReduction($price, $coeff_remise)
   {
       $reduction = $price * $coeff_remise;
       $price = $price - $reduction;
       return $price;
   }
}

La classe ProductController contient beaucoup trop de responsabilités. En effet celle-ci permet de :

  • calculer le montant ttc
  • calculer le montant ht
  • appliquer une remise

Mais que se passerait-t-il si nous avions besoin de réutiliser ces méthodes de calcul dans d’autres classes ? Au premier abord le plus simple serait de faire un copier-coller de ces méthodes aux endroits souhaitées… Erreur fatale !!

Si votre application est toute petite cela ne devrait pas être trop préjudiciable, cependant gardez présent à l’esprit que vous n’êtes pas à l’abri de modifications ou bien votre client peut souhaiter ajouter (et croyez-moi cela arrive très souvent 🙂 ) de nouvelles fonctionnalités.

Au final les doublons de vos méthodes vont rapidement augmenter et votre code sera difficilement maintenable. De plus si vous devez modifier une méthode pour une quelconque raison vous devrez alors faire un chercher remplacer partout ce qui peut être assez fastidieux et potentiellement dangereux en générant des bugs de régressions.

Bon ça y ‘est je vous ai convaincu ? Oui ? Alors maintenant je vous dis comment faire !

On va commencer par refactoriser  notre code.

<?php
/**
 * Class GestionTarif
 */
class GestionTarif
{
    public function getTarifTtc($price)
    {
        return $this->getTarifHt($price) * (1 + (20 / 100));
    }
    
    /**
     * Méthode pour calculer le tarif Ht
     * @param $price
     * @return mixed
     */
    public function getTarifHt($price)
    {
        return $price / (1 + (20 / 100));
    }
    
    /**
     * Méthode pour appliquer un pourcentage de remise
     * @param $price
     * @param $coeff_remise
     * @return mixed
     */
    public function applyReduction($price, $coeff_remise)
    {
        $reduction = $price * $coeff_remise;
        $price     = $price - $reduction;
        return $price;
    }
}

Pour l’instant c’est assez simple nous avons juste déplacer les méthodes dans une classe dédiée “GestionTarif”. Cette classe aura pour seule et unique fonction de modifier la valeur des tarifs soumis au travers de ses différentes méthodes.

Refactorisons maintenant notre contrôleur :

<?php

class ProductController
{
    /**
     * ProductController constructor.
     */
    public function __construct(GestionTarif $gestionTarif)
    {
        $this->gestionTarif = $gestionTarif;
    }
    
    /**
     * @return array
     */
    public function storeInvoice()
    {
        $prix_ttc = $this->gestionTarif->getTarifTtc($_POST['amount']);
        
        $data = array(
            'prix_ttc' => $prix_ttc,
            'prix_ht' => $this->gestionTarif->getTarifHt($_POST['amount']),
            'montant_remise' => $this->gestionTarif->applyReduction($prix_ttc, '0.5')
        );
        
        return $data;
    }
}

Vous constatez désormais que notre contrôleur est beaucoup plus light et respecte le principe “Fat model, skinny controller”. Nous avons ajouté un constructeur au contrôleur en lui injectant la classe GestionTarif.

Ainsi nous pouvons faire appel à toutes les méthodes présentes dans celle-ci. Ca sent le travail bien fait.

Avantages de la responsabilité unique

Clarté du code

Cette manière d’écrire du code permet d’isoler les fonctionnalités de votre application et par conséquent d’avoir des classes qui contiennent très peu de ligne et donc plus facile à lire et à maintenir. De plus si un autre développeur est amené à travailler sur le projet ce sera plus simple pour lui de s’y retrouver.

Débuggage

Le débuggage est simplifiée car votre fonctionnalité réside dans un seul et unique fichier. Une fois le bug identifié, il vous suffira de le corriger et toutes les classes faisant appel à votre fonctionnalités hériteront alors du correctif.

Cohérence

L’utilisation de ce design pattern vous permettra de segmenter votre application en fonctionnalités distinctes en vous basant par exemple sur le cahier des charges fourni par le client.

Tests

Les tests unitaires seront très faciles à mettre en place car la logique de votre application sera isolée dans des fichiers distincts ce qui est idéal pour lancer vos tests.

Inconvénients

Organisation des classes

Après avoir créé un fichier pour chaque classe, vous constaterez que le nombre de fichiers va très vite augmenter et que si vous n’êtes pas bien organisé vous serez vite perdu. D’où la nécessité de bien nommer vos classes avec un nom explicite.

Courbe d’ apprentissage

Si vous êtes junior dans le développement PHP, cela vous paraîtra sans doute très superflux au début. Mais avec le temps et l’expérience vous serez à même de maîtriser toutes les subtilités de cette manière de développer et cela vous permettra de vous poser les bonnes questions pendant vos développements :

  • Est-ce que cette fonctionnalité est utilisée à un autre endroit de mon application ?
  • Est-elle potentiellement susceptible d’évoluer dans le temps ?
  • Si oui puis-je l’isoler dans une classe séparée ?

Conclusion

En conclusion je dirai que le principe de responsabilité unique offre beaucoup plus d’avantages que d’inconvénients. D’ailleurs au sein de TroisPointZéro, nous l’utilisons au quotidien dans nos développements. Nous l’utilisons d’autant plus lorsque les contraintes métiers sont très fortes et évoluent durant le projet.

N’hésitez pas à commenter si vous avez des questions ou des remarques !