PHP     Symfony 2 et 3       AngularJS   Angular2       Cordova/Ionic 2
Daniel G.
Développeur d'applications Web et mobiles - Gironde

TUTORIEL

Développer en objet sans respecter un minimum de bonne pratique amène à un code difficilement maintenable, modifiable, testable.

C'est pour cela qu'à été inventé S.O.L.I.D, des principes de base pour rendre votre code plus fiable, plus maintenable et plus testable.
La théorie :
S.O.L.I.D     Les 5 principes de base :

  • Single Responsability Principle : une classe doit avoir une et une seule responsabilité
    Exit les classes fourre-tout.
  • ouvert/fermé (Open/closed principle) : une classe doit être ouverte à l'extension, mais fermée à la modification
    Enrichir une classe sans la modifier.
  • Substitution de Liskov (Liskov substitution Principle) : Pouvoir remplacer une instance par une sous instance sans modifier la cohérence du programme.
    Par exemple que le type retourné par une sous instance soit du même type que celui retourné par la classe parente.
  • Ségrégation des interfaces (Interface segregation principle) : une interface -> un rôle.
    Comme pour une classe. Par contre une interface peut hériter de plusieurs interfaces.
  • Inversion des dépendances (Dependency Inversion Principle) : il faut dépendre des abstractions, pas des implémentations.
    mauvaises pratiques : Une classe dépend de une ou plusieurs implémentations concrètes -> new .....
La pratique :
Exemple de projet :
Nous voulons formatter en texte le contenu d'un fichier xml ou d'un fichier json.
Pour cela, la classe principale va devoir recevoir n'importe quel type de fichier (.xml, .json...) et en sortir un texte.

Pour le tout, nous utiliserons le   pattern Strategy . Pour rappel, celui-ci permet de permuter dynamiquement les algorithmes utilisés. Ainsi, l'algorithme de conversion est adapté en fonction du type de fichier .
Interface segregation principle : FileLoaderInterface aura pour seul comportement celui de charger un fichier.

        interface FileLoaderInterface
        {
            public function load($file);
        }
        
Liskov substitution Principle : Vérifier qu'en entrée on a bien un fichier et en sortie un tableau et ce, quelque soit le fichier (json, xml...).

        abstract class AbstractLoader implements FileLoaderInterface
        {
               /*
                *   en entrée :   file
                *   en sortie :   array
                */
            public function load($file)
            {
                if (!file_exists($file)) {
                throw new \InvalidArgumentException(sprintf('%s does not exist.', $file));
                }
                return [];
            }
        }
        
Les 2 types de fichiers, json et xml. A l'avenir, il suffit de créer une nouvelle classe pour un nouveau type de fichier.

                class JsonFileLoader extends AbstractLoader
                {
                    public function load($file)
                    {
                        $records = parent::load($file);         // verification de l'entrée et sortie

                        $json_source = file_get_contents($file);
                        $data = json_decode($json_source, true);
                        foreach (  $data['contacts'] as $record) {
                            $records[] = $record['nom'];
                        }

                        return $records;                        // array
                    }
                }
            

                class XmlFileLoader extends AbstractLoader
                {
                    public function load($file)
                    {
                        $records = parent::load($file);         // verification de l'entrée et sortie

                        $data = simplexml_load_file($file);
                        foreach ($data as $record) {
                            $records[] = (string) $record;
                        }

                        return $records;                        // array
                    }
                }
            
Single Responsability Principle : Une classe DataFormatter ayant une reponsabilité celui de formatter.
Open/closed principle : La classe DataFormatter doit pouvoir accepter un format xml, json mais aussi d'autres formats à l'avenir.
Pour cela la classe DataFormatter acceptera toute injection de format ayant l'interface FileLoader
Dependency Inversion Principle : La classe DataFormatter n'est pas lié à un quelconque autre objet et donc aucune implementation.

                class DataFormater
                {
                    private $loader;

                    public function __construct(FileLoaderInterface $loader)
                    {
                        $this->loader  = $loader;
                    }

                    public function formatTexte($file)
                    {
                        $texte = 'Les contacts : ';
                        foreach ($this->loader->load($file) as $record) {       // String
                            $texte .= $record .' ';
                        }
                        return $texte;
                    }
                }
            
Utilisation avec ces 2 fichiers :
data.json

                {"contacts": [
                    {"nom":"Paul"},
                    {"nom":"Virginie"},
                    {"nom":"Claire"}
                ]}

            
data.xml

                    <?xml version="1.0" encoding="utf-8"?>
                    <contacts>
                        <nom>Damien</nom>
                        <nom>Philippe</nom>
                        <nom>Claire</nom>
                    </contacts>
            
php

                $dataFormater = new DataFormater(new JsonFileLoader());
                $texte = $dataFormater->formatTexte('data.json');
                echo $texte;

                Affiche -> Paul Virginie Claire


                $dataFormater = new DataFormater(new XmlFileLoader());
                $texte = $dataFormater->formatTexte('data.xml');
                echo $texte;

                Affiche -> Damien Philippe Claire
            

TUTORIEL : Injection de dépendances

J'ai décidé d'en parler ici car l'injection de dépendance respecte notamment le D de S.O.L.I.D que nous avons vu plus haut.
Le but de ce mécanisme est de découpler les objets pour simplifier la communication entre les objets.
En effet, deux objets fortement liés rend le code peu évolutif et modifier ce genre de code augmente le risque de régression et de bugs.
Le projet :
Une place de parking que l'on associe à une voiture.
On a donc une classe PlaceParking qui possède pour propriété une classe Voiture.


Pour comprendre la problématique, voici la façon classique de faire :

            class Voiture {
                private $immatriculation;

                public function __construct($immatriculation){
                    this->immatriculation = $immatriculation;
                }
            }

            class PlaceParking{
                private $voiture;

                public function __construct($immatriculation){
                    $this->voiture = new Voiture($immatriculation);
                }
            }

            $emplacement = new PlaceParking("4242 fc 33");

            
Dans cet exemple, la classe voiture a été instanciée dans le constructeur de PlaceParking.
voiture et PlaceParking sont donc fortement liés.

Les problèmes :
  1. Si plus tard, nous voulons ajouter une propriété "marque" à la classe Voiture ?
    Il faudra ajouter cette propriété dans Voiture et modifier en conséquence le constructeur de PlaceParking
  2. Si plus tard, en plus de la voiture nous voulons associer une moto ?
    Il faudra créer une classe Moto. Dans la classe PlaceParking ajouter une propriété moto et dans le constructeur, détecter si c'est Voiture ou Moto qui est envoyé (avec instanceOf)
Dans tous les cas, il faut modifier la classe Voiture et plus embêtant encore, la classe PlaceParking.



Solution :

1


Pour ajouter une propriété, nous allons faire cette implémentation :

                    class Voiture {
                        private $immatriculation;
                        private $marque;

                        public function __construct($immatriculation, $marque){
                            this->immatriculation = $immatriculation;
                            this->marque = $marque;
                        }

                        // getter et setter
                    }

                    class PlaceParking{
                        private $voiture;

                        public function __construct(Voiture $voiture){
                            $this->voiture = $voiture;
                        }
                    }

                    $voiture = new Voiture("4242 fc 33", "Peugeot");
                    $emplacement = new PlaceParking($voiture);

            
La propriété marque a été ajouté à la classe Voiture sans toucher à PlaceParking

Et, par la même occasion nous venons de faire de l'injection de dépendances !
En effet, nous avons injecté dynamiquement l'objet voiture à l'objet emplacement (PlaceParking).


2


Mais

, certes PlaceParking et Voiture sont moins couplés qu'auparavant mais ils le sont encore un peu.
PlaceParking dépend trop du type Voiture et cela nous amène à la question 2 : et si nous voulons inclure une moto ?

voici la solution :

                    interface VehiculeInterface
                    {
                        public function getInformations();
                    }


                    class Voiture implements VehiculeInterface {
                        protected $immatriculation;
                        protected $marque;
                        protected $permis = "B";
                        protected $motorisation;

                        public function __construct($immatriculation, $marque, $motorisation){
                            $this->immatriculation = $immatriculation;
                            $this->marque = $marque;
                            $this->motorisation = $motorisation;
                        }
                        public function getInformations(){
                            return "voiture : " .  $this->immatriculation . " " . $this->marque . " " . $this->permis  . " " . $this->motorisation;
                        }
                        // getter et setter
                    }

                    class Moto implements VehiculeInterface {
                        protected $immatriculation;
                        protected $marque;
                        protected $permis = "C";
                        protected $motorisation;

                        public function __construct($immatriculation, $marque){
                            $this->immatriculation = $immatriculation;
                            $this->marque = $marque;
                        }
                        public function getInformations(){
                            return "moto : " .  $this->immatriculation . " " . $this->marque . " " . $this->permis;
                        }
                        // getter et setter
                    }

                    class PlaceParking{
                        protected $vehicule;

                        public function __construct(VehiculeInterface $vehicule){
                            $this->vehicule = $vehicule;
                        }

                        public function getInformations() {
                            return $this->vehicule->getInformations();
                        }
                    }
            

                    $voiture = new Voiture("4242 pe 33", "Peugeot", "diesel");
                    $emplacement = new PlaceParking($voiture);

                    $moto = new Moto("4242 ya 33", "yamaha");
                    $emplacement2 = new PlaceParking($moto);

                    print_r($emplacement->getInformations());
                    print_r($emplacement2->getInformations());
            

Le découplage est cette fois si optimisé, nous pouvons injecter n'importe quel type de vehicule.
Pour ajouter un nouveau type, il suffit de créer une nouvelle classe et c'est tout (aucune modification n'est nécessaire dans le code).

De plus, vous pouvez ajouter une nouvelle propriété dans Voiture ou Moto. Vous le faites sans même modifier PlaceParking.



remarque : Voiture et Moto ont des propriétés et méthodes en commun, je pouvais factoriser du code avec une classe abstract mais pour rester simple dans l'exemple je ne l'ai pas fait.