Archives de catégorie : PHP

PHP : le design pattern Observateur

Observateur, un design pattern comportemental

Après avoir vu Adaptateur, Décorateur, Template Method ou Factory, nous allons nous concentrer sur un design pattern comportemental : Observateur. Comme tous les design patterns comportementaux (au sens GoF du terme), Observateur décrit la manière dont des objets interagissent entre eux.

Quels sont ces objets ?

Ce design pattern met en jeu deux types d’objets :

  • un sujet
  • des observateurs

Un sujet notifie un ou plusieurs observateurs qu’un changement d’état vient de se produire chez lui.

Personne observant le ciel aux jumelles

Un observateur attendant avec impatience une notification en provenance d’un sujet.

Dans quel état j’erre ?

Le sujet possède un état interne; lorsque cet état est altéré, il va notifier ses observateurs de ce changement. Prenons un exemple trivial, qui va parler au plus grand nombre : lorsqu’une personne présente sur un réseau social (notre sujet) fête son anniversaire, son état change car sa propriété âge est incrémenté d’une unité et quiconque suit ce sujet (un observateur, donc) reçoit une notification l’avertissant de ce changement d’état. L’observateur se met à jour et affiche la bonne nouvelle à l’écran: « X fête son anniversaire, n’oubliez pas de lui souhaiter ! »…

Structure du design pattern Observateur

Voici la version telle que définie par le Gang of Four :

Structure du design pattern Observateur

Toute la puissance de ce design pattern réside dans le fait que sa structure, à base d’abstractions, induit un couplage faible entre les deux types d’objets qui le composent : un sujet ne sait rien du type concret de ses observateurs, si ce n’est qu’il se conforme en tous points à l’interface Observer, pas plus qu’il n’est au courant de leur nombre. Le sujet n’aura donc aucunement à se risquer à d’hasardeuses hypothèses sur la nature de ceux qui l’observent et n’importe quel objet sera en mesure de l’observer, du moment que la classe concrète dont il est issu implémente l’interface Observer.

Lorsque j’évoque ici la notion d’interface, c’est au sens large du terme et pas stricto sensu. A l’origine – voilà maintenant 20 ans – GoF préconisait l’utilisation de classes abstraites, à dériver en classes concrètes. Mais vous ne le savez que trop bien, l’héritage n’est pas une panacée universelle, loin s’en faut.

Quelques points clés de ce modèle :

  • Sujet connait l’ensemble de ses observateurs. Leur nombre n’est pas limité.
  • Les observateurs s’enregistrent lorsqu’ils veulent observer un sujet et s’en détachent lorsqu’ils ne le veulent plus.
  • Lorsque son état est altéré, Sujet notifie l’ensemble de ses observateurs, sans notion d’ordre et sans distinction !
  • Lorsqu’il est notifié, un observateur peut obtenir des informations en provenance du sujet. Il peut décider de gérer une notification ou bien de l’ignorer.
  • Rien n’interdit à un observateur de suivre plusieurs sujets. Il faut évidemment que cet observateur puisse déterminer l’origine des notifications qu’il reçoit.

Un petit bémol, toutefois : en l’état actuel des choses, la fonction de mise à jour de l’observateur (Update) ne permet pas de savoir ce qui a changé dans le sujet, laissant à la charge de l’observateur la responsabilité de « deviner » ce qui a entrainé une modification.

La mise à jour, côté observateur

Nous allons voir comment à l’autre bout du fil, les observateurs gèrent leur mise à jour d’état avec la fonction Update.

Tu tires ou tu pousses ?

Deux modes de fonctionnement s’offrent à nous :

  • par poussée (push)
  • par traction (pull)

Le mode push – « Tiens, prends ça ! »

Le sujet pousse des informations aux observateurs. L’inconvénient de cette façon de faire est que le lien entre le sujet et ses observateurs se resserre. Le sujet peut se retrouver à pousser des informations dont certains observateurs n’auront pas besoin. Que pousser et à qui ?

Le mode pull – « Viens te servir ! »

Le sujet notifie simplement ses observateurs qu’un changement vient d’avoir lieu, à eux de savoir ce qui a changé !

Des versions basiques d’Observateur

Les contrats à respecter

Vous le savez maintenant, un sujet notifie N observateurs. On lui attache/détache ces observateurs à l’aide des méthodes appropriées et il les informe d’un changement via l’appel à la méthode mettreAJour de chacun d’entre eux. Voici à quoi vont ressembler nos contrats d’interface (dans la version GoF, ces abstractions sont des classes abstraites, ici nous utilisons des interfaces).

interface SujetInterface
{
    public function attacher(ObservateurInterface $observateur): void;
    public function detacher(ObservateurInterface $observateur): void;
    public function notifier(): void;
}

interface ObservateurInterface
{
    public function mettreAJour(SujetInterface $sujet): void;
}

La notification en version pull

Le sujet

Notre sujet stocke l’ensemble de ses observateurs dans une variable d’instance privée prévue à cet effet. Ce tableau est rempli ou vidé au fur et à mesure que les observateurs sont attachés ou détachés. Un setter mettreAJourLesNouvelles est prévu pour modifier la variable d’instance $nouvelles et un getter donnerLesNouvelles servira aux observateurs pour aller récupérer le dernier état du sujet. Souvenez-vous qu’en mode pull, ce sont les observateurs qui doivent se renseigner sur l’état du sujet et éventuellement mettre à jour le leur avec ces informations tirées depuis le sujet.

class Sujet implements SujetInterface
{
    private $nouvelles;

    private $observateurs;
    
    public function attacher(ObservateurInterface $observateur): void
    {
        $this->observateurs[] = $observateur;
    }
 
    public function detacher(ObservateurInterface $observateur): void
    {
        $key = array_search($observateur, $this->observateurs);
 
        if (false !== $key) {
            unset($this->observateurs[$key]);
        }
    }
 
    public function notifier(): void 
    {
        foreach ($this->observateurs as $observateur) {
            $observateur->mettreAJour($this);
        }
    }

    public function mettreAJourLesNouvelles(string $nouvelles): void 
    {
        $this->nouvelles = $nouvelles;
        $this->notifier();
    }
    
    public function donnerLesNouvelles(): string 
    {
        return $this->nouvelles;
    }
}

L’observateur

C’est lui qui fait le travail, une fois qu’il est notifié à travers sa méthode mettreAJour. Il reçoit le sujet à l’origine de la notification de mise à jour car il peut observer plusieurs sujets; il nous faut donc impérativement savoir qui a changé parmi les objets qu’on observe. Ici nous avons réduit la structure de la classe à sa plus simple expression: l’observateur reçoit une notification de nouvelle de la part d’un sujet, il va TIRER l’information depuis ce sujet et mettre son état interne en accord avec celui du sujet, afin d’avoir les toutes dernières nouvelles.

class Observateur implements ObservateurInterface
{
    private $dernieresNouvelles;
    
    public function mettreAJour(SujetInterface $sujet): void
    {
        $this->dernieresNouvelles = $sujet->donnerLesNouvelles();
    }
}

Le code client

Voici le code pour utiliser cette structure en mode pull, il est très simple: nous créons deux observateurs que nous attachons à notre unique sujet et nous mettons à jour l’état de notre sujet, qui va déclencher deux notifications (une pour chaque observateur).

$sujet = new Sujet();
$observateur = new Observateur();
$autreObservateur = new Observateur();
$sujet->attacher($observateur);
$sujet->attacher($autreObservateur);
$sujet->mettreAJourLesNouvelles("La bourse dévisse !");

La notification en version push

Changement d’interface

En mode push, c’est le sujet qui pousse les données modifiées à ses observateurs. Il sait donc ce qu’il leur faut, ce qui induit un couplage plus serré que dans le mode pull. Il peut aussi leur passer son état complet mais attention si les informations sont volumineuses…Les observateurs ont-ils besoin de recevoir un sac postal complet alors qu’ils attendent une carte postale ?

Quoiqu’il en soit, l’interface de l’observateur change et donc l’appel de la méthode aussi, dans le sujet:

interface ObservateurInterface
{
    public function mettreAJour(SujetInterface $sujet, array $donnees): void;
}

Le sujet

Le sujet fera désormais:

    public function notifier(): void 
    {
        foreach ($this->observateurs as $observateur) {
            $observateur->mettreAJour($this, ["nouvelles" => $this->nouvelles]);
        }
    }

L’observateur

L’observateur ira quant à lui se servir à ladite clé dans le tableau $donnees, qui dans la pratique sera évidemment bien plus fourni:

    public function mettreAJour(SujetInterface $sujet, array $donnees): void
    {
        $this->dernieresNouvelles = $donnees["nouvelles"];
    }

Le code client ne bougera pas d’un pouce, c’est la cuisine interne de l’objet qui change très légèrement. Le principe est rigoureusement identique !

Notre version d’Observateur

Qu’est-ce qui interdit à nos sujets d’être des observateurs d’autre sujets ? Rien !
Nous allons simuler un réseau social.

Nos interfaces

Elles ne changent pas beaucoup des précédentes:

interface SujetInterface
{
    public function attacher(ObservateurInterface $observateur): void;
    public function detacher(ObservateurInterface $observateur): void;
    public function notifier(): void;
}

interface SuiveurInterface
{
    public function suivre(SujetInterface $sujet): void;
    public function nePlusSuivre(SujetInterface $sujet): void;
}

interface ObservateurInterface
{
    public function mettreAJour(SujetInterface $sujet): void;
}

Nous avons juste rajouté une interface SuiveurInterface qui liste ce que doit savoir faire un suiveur: suivre et cesser de suivre.

Le membre

Dans notre réseau social, nous avons deux types de membres: des personnes et des entreprises ou des associations qui ont des pages. Ces deux catégories de membres peuvent suivre des membres, comme sur Facebook. Pouvant être à la fois sujet et observateur, ils doivent implémenter l’ensemble des interfaces. Nous factorisons les comportements dans une classe abstraite qui va contenir pour un membre, les membres qu’il suit et les membres qui le suivent. Les membres suivis iront dans $sujets et les suiveurs, dans $observateurs.

Faisons simple: de base, un membre a juste un nom et nous mettons un getter dessus, même pas de setter.
Vous constatez que la méthode mettreAJour ne contient que le sujet, la notification se fera donc en mode pull.

abstract class AbstractMembre implements SujetInterface, SuiveurInterface, ObservateurInterface
{
    protected $nom;
    
    protected $observateurs;
    
    protected $sujets;
    
    public function __construct(string $nom)
    {
        $this->nom = $nom;
    }
    
    public function attacher(ObservateurInterface $observateur): void
    {
        echo $this->donnerNom().PHP_EOL;
        echo "\t".$observateur->donnerNom()." vous à ajouté à ses contacts".PHP_EOL;
        $this->observateurs[] = $observateur;
    }
 
    public function detacher(ObservateurInterface $observateur): void
    {
        $key = array_search($observateur, $this->observateurs);
 
        if (false !== $key) {
            echo $this->donnerNom().PHP_EOL;
            unset($this->observateurs[$key]);
            echo "\tVous avez enlevé ".$observateur->donnerNom()." de vos contacts".PHP_EOL;
        }
    }
 
    public function notifier(): void 
    {
        foreach ($this->observateurs as $observateur) {
            $observateur->mettreAJour($this);
        }
    }

    public function donnerNom(): string
    {
        return $this->nom;
    }
    
    public function suivre(SujetInterface $sujet): void
    {
        // pour éviter qu'un membre puisse s'auto-suivre
        if ($sujet === $this) {
            return;
        }
        
        $this->sujets[$sujet->donnerNom()] = clone $sujet;
        $sujet->attacher($this);
    }
    
    public function nePlusSuivre(SujetInterface $sujet): void
    {
        echo $this->donnerNom().PHP_EOL;
    
        $key = array_search($sujet, $this->sujets);
 
        if (false !== $key) {
            unset($this->sujets[$key]);
        }
        
        echo "\tVous ne suivez plus ".$sujet->donnerNom().PHP_EOL;
    }
    
    public function mettreAJour(SujetInterface $sujet): void
    {
        echo $this->donnerNom().PHP_EOL;
        $nom = $sujet->donnerNom();
        
        if (array_key_exists($nom, $this->sujets)) {
            $sujetStocke = $this->sujets[$nom];
            
            if ($sujet instanceof PageCommerciale) {
                $urlMagasin = $sujet->donnerUrlMagasin();
                $urlMagasinStocke = $sujetStocke->donnerUrlMagasin();
                
                if ($urlMagasin !== $urlMagasinStocke) {
                    echo "\tLe site web de $nom vaut désormais $urlMagasin";
                }

            } elseif ($sujet instanceof Membre) {
                $age = $sujet->donnerAge();
                $ageStocke = $sujetStocke->donnerAge();

                if ($ageStocke !== $age) {
                    echo "\t$nom fête son anniversaire, il a $age ans";
                    $this->sujets[$nom] = clone $sujet;
                }
                
                $hobbies = $sujet->donnerHobbies();
                $hobbiesStocke = $sujetStocke->donnerHobbies();

                if ($hobbiesStocke !== $hobbies) {
                    echo "\t$nom a de nouveaux hobbies: ".implode(", ", $hobbies);
                    $this->sujets[$nom] = clone $sujet;
                }
            }
            echo PHP_EOL;
        }
    }
}

Les classes concrètes

On en compte deux, une pour les membres et une pour les pages commerciales. Un membre a un âge et des hobbies, une page commerciale ne comporte que l’URL du site marchand. Là aussi j’ai vraiment gardé l’essentiel dans chaque classe, qui est sur-simplifiée. Voici notre classe Membre:

final class Membre extends AbstractMembre {
 
    private $age;
     
    private $hobbies;
     
    public function changerAge(int $age): void {
        $this->age = $age;
        $this->notifier();
    }
     
    public function changerHobbies(array $hobbies): void {
        $this->hobbies = $hobbies;
        $this->notifier();
    }
 
    public function donnerAge(): ?int {
        return $this->age;
    }
    
    public function donnerHobbies(): ?array {
        return $this->hobbies;
    }
}

et voici notre classe PageCommerciale:

final class PageCommerciale extends AbstractMembre {
 
    private $urlMagasin;
     
    public function changerUrlMagasin(string $urlMagasin): void {
        $this->urlMagasin = $urlMagasin;
        $this->notifier();
    }
 
    public function donnerUrlMagasin(): ?string {
        return $this->urlMagasin;
    }
}

Chaque classe comporte des setters qui vont modifier l’état des objets et donc générer des notifications aux objets qui les observent.

Retour sur la méthode mettreAJour

    public function mettreAJour(SujetInterface $sujet): void
    {
        echo $this->donnerNom().PHP_EOL;
        $nom = $sujet->donnerNom();
        
        if (array_key_exists($nom, $this->sujets)) {
            $sujetStocke = $this->sujets[$nom];
            
            if ($sujet instanceof PageCommerciale) {
                $urlMagasin = $sujet->donnerUrlMagasin();
                $urlMagasinStocke = $sujetStocke->donnerUrlMagasin();
                
                if ($urlMagasin !== $urlMagasinStocke) {
                    echo "\tLe site web de $nom vaut désormais $urlMagasin";
                }

            } elseif ($sujet instanceof Membre) {
                $age = $sujet->donnerAge();
                $ageStocke = $sujetStocke->donnerAge();

                if ($ageStocke !== $age) {
                    echo "\t$nom fête son anniversaire, il a $age ans";
                    $this->sujets[$nom] = clone $sujet;
                }
                
                $hobbies = $sujet->donnerHobbies();
                $hobbiesStocke = $sujetStocke->donnerHobbies();

                if ($hobbiesStocke !== $hobbies) {
                    echo "\t$nom a de nouveaux hobbies: ".implode(", ", $hobbies);
                    $this->sujets[$nom] = clone $sujet;
                }
            }
            echo PHP_EOL;
        }
    }

Cette méthode est au niveau de la classe abstraite; elle fait une vérification sur le type concret des sujets car les informations à tirer depuis ce sujet ne seront pas les mêmes pour les observateurs concrets. Vous voyez arriver le problème si nous rajoutons des classes concrètes en bas de l’arbre d’héritage, votre if va enfler et il vous faudra peut-être avoir recours à un nouveau design pattern pour gérer ces différentes façons d’opérer selon les types concrets, lequel ?

Notez que nous clonons les objets; il nous faut fossiliser leur état à l’instant t pour pouvoir le comparer avec celui du sujet reçu en paramètre de la méthode.

Le code client

$magasin = new PageCommerciale('ACME Web Store');
$star = new Membre('Rasmus Lerdorf');
$copain = new Membre('Jean-Michel Apeuprey');
$autreStar = new Membre('Novak Djokovic');
$membre = new Membre('Sébastien Ferrandez');

$membre->suivre($magasin);
$membre->suivre($star);
$membre->suivre($autreStar);
$membre->suivre($copain);
$copain->suivre($membre);
$magasin->suivre($autreStar);
$copain->changerAge(41);
$membre->changerHobbies(["guitare"]);
$star->changerAge(36);
$star->changerHobbies(["linux", "c++", "c"]);
$autreStar->changerHobbies(["musculation", "karaoké"]);
$magasin->changerUrlMagasin('http://www.acmestore.com');
// Je n'aime plus le tennis !
$membre->nePlusSuivre($autreStar);
// Je me suis fâché avec Jean-Michel, il n'est plus mon ami !
$membre->detacher($copain);

TADAAAM !

Voici la sortie générée par l’exécution du code client, on n’est pas trop mal on dirait ! N’hésitez pas à proposer des alternatives ou poser des questions dans les commentaires !

Sortie écran pour les observateurs

Où trouve t-on des implémentations d’Observateur ?

Typiquement, dans la programmation par événements où des classes souscrivent auprès d’une classe gestionnaire d’évènements qui les informera lorsque l’évènement auquel elles ont souscrit se produit (en appelant parfois des fonctions de callback).

Lorsque vous cliquez sur un article pour l’ajouter et que votre panier se met à jour, vous avez un bel exemple d’Observateur.

Sur les réseaux sociaux, comme dans notre exemple trivial, nous recevons des notifications dès qu’un événement d’une personne que l’on suit se produit (changement de statut, nouveau post etc.)

En PHP, la SPL propose des classes pour mettre en place le design pattern Observateur avec SPLObserver.

Enfin dans Symfony, les évènements sont gérés selon ce principe…Les observateurs sont des listeners ou des event subscribers auquel on dispatche des notifications contenant des events.

PHP : le design pattern Adaptateur

Chez Auto Load, tout est permis !

Pour illustrer notre exemple du design pattern Adaptateur, nous allons cette fois nous prendre pour des inspecteurs du permis de conduire ! Chez AUTO LOAD, nous nous vantons d’obtenir un taux de réussite de 99% à l’examen du permis de conduire et ce sans soudoyer de quelque manière que ce soit nos candidats ! Voyons sans tarder les principales classes qui composent notre exemple. Tout d’abord, la classe InspecteurPermisConduire, qui est notre classe cliente. C’est elle qui va faire usage de nos autres classes.

class InspecteurPermisConduire {
    private $_candidat;
	
    public function __construct(ConducteurInterface $conducteur)
    {
        $this->_candidat = $conducteur;
    }
    public function changerCandidat(ConducteurInterface $conducteur)
    {
        $this->_candidat = $conducteur;
    }
    public function fairePasserExamen()
    {
        $this->_candidat->demarrer();
        $this->_candidat->accelerer();
        $this->_candidat->tournerDroite();
        $this->_candidat->accelerer();
        $this->_candidat->tournerGauche();
        $this->_candidat->ralentir();
        $this->_candidat->reculer();
        $this->_candidat->immobiliser();
    }
}

Une instance donnée d’InspecteurPermisConduire prend en composition tout objet se conformant à l’interface ConducteurInterface : tout d’abord, elle l’exige dans son constructeur, c’est à dire qu’aucune instance d’InspecteurPermisConduire ne peut exister sans un objet qui implémente en totalité ConducteurInterface. Ensuite, nous nous réservons la possibilité de changer de type de candidat à l’exécution (runtime) grâce à un setter. Enfin, la valeur ajoutée de notre classe réside dans sa méthode fairePasserExamen, qui fait effectuer à tout conducteur les manœuvres de base, indispensables pour décrocher le précieux sésame !

Voici justement notre Automobiliste :

interface ConducteurInterface {
	public function demarrer();
	public function tournerGauche();
	public function tournerDroite();
	public function accelerer();
	public function ralentir();
	public function reculer();
	public function immobiliser();
}

class Automobiliste implements ConducteurInterface {
	public function demarrer() {
		echo "tourner la clé de contact ou mettre la carte";
	}
	public function tournerGauche() {
		echo "tourner le volant vers la gauche";
	}
	public function tournerDroite() {
		echo "tourner le volant vers la droite";
	}
	public function accelerer() {
		echo "appuyer sur la pédale d'accélération";
	}
	public function ralentir() {
		echo "relâcher la pédale d'accélération et/ou", 
                      "appuyer sur la pédale de frein";
	}
	public function reculer() {
		echo "passer la marche arrière et accélérer";
	}
	public function immobiliser() {
		echo "mettre le frein à main";
	}
}

Notre classe concrète Automobiliste implémente les fonctions imposées par l’interface ConducteurInterface. Cette interface recense les principales manoeuvres dont la maîtrise est exigée de tout conducteur de véhicule qui se respecte : démarrer et arrêter le véhicule, avancer, reculer, tourner, accélérer et ralentir. Attention cependant, certains véhicules – comme un vélo – ne se démarrent pas, il va falloir prendre en compte cet aspect là…

Bref, à ce stade là, tout est pour le mieux dans le meilleur des mondes ! [Candide]

Schéma UML de l'exemple de design pattern adaptateur

Dès que le vent soufflera, je recodera… (air bien connu)

Jusqu’au jour où Otto Hekohl, le propriétaire de notre école de conduite, vient nous annoncer qu’étant donné que nous sommes situés en bord de mer, il serait plutôt judicieux – en plus d’être diablement bénéfique pour notre chiffre d’affaire – que nous puissions faire passer le permis bateau à des candidats qui sont de plus en plus nombreux et demandeurs…

Photos de gens sautant d'un yacht

Elle est parfois dure la vie à bord d’un frêle esquif !

Avant de se faire congédier par notre patron pour avoir regardé trop de vidéos de chatons montant sur le dos de tortues durant ses heures de travail, Jojo Dingo, le deuxième programmeur de l’entreprise, avait envisagé d’intégrer ce changement de notre stratégie commerciale. Voici la structure logicielle dont nous héritons, maintenant que Jojo a toutes ses journées de libre pour s’adonner à sa félinophilie maladive :

interface NavigateurInterface {
	public function demarrer();
	public function reculer();
	public function tournerBabord();
	public function tournerTribord();
	public function accelerer();
	public function ralentir();
	public function jeterAncre();
}

abstract class Marin implements NavigateurInterface {
	public function jeterAncre() {
		echo "jeter l'ancre à la mer";
	}
}

class UnsupportedMethodException extends Exception {}

class MarinVoile extends Marin {
	public function demarrer() {
		throw new UnsupportedMethodException
                     ("Cette fonctionnalité n'est pas disponible");
	}
	public function tournerBabord() {
		echo "diriger les voiles et la barre pour aller à babord";
	}
	public function tournerTribord() {
		echo "diriger les voiles et la barre pour aller à tribord";
	}
	public function accelerer() {
		echo "positionner les voiles et déterminer l'allure";
	}
	public function ralentir() {
		echo "positionner les voiles et déterminer l'allure";
	}
	public function reculer() {
		echo "positionner les voiles et manœuvrer pour reculer";
	}
}

class MarinMoteur extends Marin {
	public function demarrer() {
		echo "démarrer le moteur";
	}
	public function tournerBabord() {
		echo "manoeuvrer la barre ou le volant pour aller à babord";
	}
	public function tournerTribord() {
		echo "manoeuvrer la barre ou le volant pour aller à tribord";
	}
	public function accelerer() {
		echo "augmenter la vitesse du moteur";
	}
	public function ralentir() {
		echo "dimininuer la vitesse du moteur ou le couper";
	}
	public function reculer() {
		echo "passer la marche arrière";
	}
}

Jojo a bien compris qu’il nous fallait des classes qui nous permettent de gérer les gens qui s’adonnent à la navigation de plaisance motorisée ou à voile et qu’un bateau ne se manœuvrait pas comme une voiture.

Attardons-nous sur la structure de ces classes : nous avons une classe abstraite Marin qui implémente une interface NavigateurInterface et dont les deux filles concrètes MarinVoile et MarinMoteur réalisent la majeure partie des fonctions.

Schéma UML du design pattern adaptateur

Marin est abstraite car trop générique, elle réalise cependant l’implémentation de jeterAncre, qui est commune à toute forme de navigation (bon, c’est vrai, les Optimist n’ont pas d’ancre). Jojo a choisi de créer une exception pour gérer le cas où la fonction démarrer n’est pas prise en charge, plutôt que de l’isoler dans une interface NavigateurMoteurInterface dédiée aux bateaux qui ont ce mode de propulsion. C’est un choix de conception, qui a le mérite de favoriser la généricité, inutile de faire une vérification de la présence de cette méthode en utilisant par exemple method_exists dans la classe client InspecteurPermisConduire. C’est la classe concrète qui gère l’absence de cette méthode en levant une exception correctement typée.

Comment ne pas se faire mener en bateau ?

Adeptes de longue date de la programmation orientée objet, nous souhaitons minimiser l’impact de ce changement d’architecture logicielle dans nos classes existantes. C’est là où l’adaptateur entre en jeu !

L’adaptateur fonctionne exactement sur le même principe que l’adaptateur de votre sèche-cheveux quand vous allez, en Irlande, par exemple ! Il prend en entrée la prise française de votre sèche-cheveux favori et l’adapte aux prises à trois broches que vous trouvez sur l’île Émeraude ! Vous n’avez pas eu à dénuder les fils de votre appareil pour y fixer une prise irlandaise, et heureusement : l’adaptateur fait le travail pour vous ! Et vous n’y avez vu que du bleu !

S’adapter…au changement

L’adaptateur a pour but de faire collaborer des interfaces de prime abord incompatibles, comme ConducteurInterface et NavigateurInterface. C’est un intermédiaire précieux (et réutilisable, ce qui nous met en joie !) qui va nous éviter de déstabiliser notre code base et qui va découpler le client (notre inspecteur du permis de conduire) de notre adapté (ici, nos marins). Plus le couplage est lâche, mieux c’est, je ne vous apprends rien !

L’adaptateur prend en composition l’adapté, et il sera chargé d’effectuer la translation entre les demandes à destination de l’interface originelle (ConducteurInterface) et la nouvelle interface cible (NavigateurInterface).

Pour que cette translation s’effectue de manière transparente pour le système, il faut naturellement que l’adaptateur implémente l’interface originelle. Regardez donc :

class AdaptateurMarin implements ConducteurInterface {
	private $_marin;
	
	public function __construct(Marin $marin) {
		$this->_marin = $marin;
	}
	public function demarrer() {
		$this->_marin->demarrer();
	}
	public function tournerGauche() {
		$this->_marin->tournerBabord();
	}
	public function tournerDroite() {
		$this->_marin->tournerTribord();
	}
	public function accelerer() {
		$this->_marin->accelerer();
	}
	public function ralentir() {
		$this->_marin->ralentir();
	}
	public function reculer() {
		$this->_marin->reculer();
	}
	public function immobiliser() {
		$this->_marin->jeterAncre();
	}
}

Le polymorphisme avant tout ! Notre adaptateur prend en charge des objets de la classe Marin, c’est à dire potentiellement toutes les sous-classes qui en dérivent. Tant mieux car nous en avons deux : MarinVoile et MarinMoteur !

Nous n’avons pas du tout modifié notre classe existante, nous avons isolé la nouveauté dans une nouvelle classe, autonome, avec des responsabilités clairement identifiées.

Les comportements que vous isolez dans des unités de code indépendantes sont réutilisables, ceux que vous enfermez dans des classes ne le sont pas forcément !

Pour que le système prenne en compte cette nouvelle possibilité (à nous l’argent facile !), il nous faudra juste modifier le code appelant. Notre client avait déjà un modificateur d’implémenté, à la bonne heure, servons nous en donc !

$adaptateur = new AdaptateurMarin(new MarinMoteur());
$inspecteur->changerCandidat($adaptateur);
$inspecteur->fairePasserExamen();

La méthode changerCandidat exige en paramètre une implémentation de la classe ConducteurInterface, ça tombe bien c’est précisément ce qu’est Marin et ses filles concrètes.

Souvenez vous toutefois qu’une exception est levée dans notre adaptateur MarinVoile, il faut donc en réalité modifier quelque peu le code appelant, qui va devoir les intercepter et les traiter le cas échéant. N’allons pas chercher midi à quatorze heures, nous nous contenterons d’en afficher le message. « KISS » is the law !

try {
	$inspecteur->fairePasserExamen();
} catch (UnsupportedMethodException $exception) {
	echo $exception->getMessage();
}

Voici maintenant l’architecture de notre code, avec l’adaptateur qui est venu s’intercaler entre l’existant et nos nouvelles fonctionnalités, qui vont nous permettre de gérer marins d’eau douce, flibustiers, jeunes loups de mer, moules à gaufres et autres ectoplasmes !

schema2

Pour lire cet article dans des conditions optimales, il est fortement conseillé d’écouter autant de fois que nécessaire ce titre (en plus d’aller les voir en concert en mai 2014) :

PHP : strlen vs mb_strlen

De l’utilisation dévoyée de strlen

Une erreur communément faite par nombre de développeurs PHP consiste à se servir de strlen pour mesurer la longueur d’une chaîne de caractères. Mais gare ! Car, même si la documentation PHP est quelque peu ambiguë à ce sujet, il est toutefois fait mention du fait suivant :

strlen() retourne le nombres d’octets plutôt que le nombre de caractères dans une chaîne.

Pour des chaînes de caractères ASCII (codées sur un octet – 7 bits, en réalité), pas de problèmes particulier, comme vous le constatez :

strlen

Attention toutefois, les espaces sont aussi des caractères ! Vous pouvez appliquer trim au besoin pour élaguer les espaces (avant et après, pas au milieu de votre châine de caractères !)

strlen-espace

Photo d'un ruban à mesurer

La taille, ça compte !


Là où les choses se corsent quelque peu, c’est lorsque votre chaîne de caractères est susceptible de contenir des caractères codés sur plus d’un octet…

strlen-utf8

La fonction strlen me renvoie 10…elle ne compte pas les caractères (il y en a 9 !) mais les octets nécessaires pour représenter notre chaîne de caractères ! Attention donc aux étourdis qui pensent compter le nombre de caractères, vous faites fausse route ! Mon caractère accentué compte pour deux, mais deux octets…et non deux caractères !

mb_strlen à la rescousse !

Avant de voir ce que donnerait mb_strlen appliquée à notre exemple, tâchons de voir quel codage de caractères nous avons par défaut :
strlen-internal-encoding

Par défaut, notre jeu de caractères est donc latin1. Si nous utilisons mb_strlen sans préciser le jeu de caractères que nous ciblons, le résultat sera le même qu’en utilisant strlen, et ça, nous n’en voulons pas.

Voyons quel est le jeu de caractères de notre chaîne, même si nous nous en doutons un peu ! 😉
7

Nous y voilà donc ! Notre chaîne est codée en UTF-8. Cette information est capitale si nous souhaitons utiliser mb_strlen correctement. Voici donc l’appel à cette fonction tel qu’il doit être fait pour fonctionner :

7

La fonction mb_strlen nous renvoie bien 9, qui est bien le nombre de caractères de notre chaîne !

Attention, le type de retour de mb_strlen est mixed, ce qui signifie que la fonction renvoie plusieurs types :

  • Dans le cas où elle s’exécute correctement, elle renvoie bien le nombre de caractères selon le codage que vous avez choisi (ou celui par défaut)
  • Dans le cas où elle échoue, elle renvoie le booléen false

Faites donc attention à utiliser la triple égalité (en valeur et en nombre). Si vous passez un codage invalide, la fonction vous renverra false et un Warning. Attention cependant pour les petits malins qui abusent du @ pour cacher les erreurs sous le tapis :
+strlen-badencoding

Que votre chaîne soit vide ou que vous mentionniez un codage invalide, avec une égalité simple (non stricte), le résultat sera le même.

Les tests unitaires dans Eclipse PDT avec MakeGood

MakeGood est un plug-in pour Eclipse qui vous permettra de lancer de manière continue (à l’issue d’une simple modification du code) vos tests unitaires. Rien de plus énervant que de devoir alterner entre ligne de commande et IDE pour développer, n’est-ce pas ? Ce plug-in, développé par une entreprise japonaise, est là pour mettre un terme à vos souffrances de développeur PHP consciencieux ! Voyons sans plus tarder comment il s’installe. Je suppose évidemment que vous respectez les pré-requis, à savoir une installation fonctionnelle d’Eclipse PDT, de phpUnit (via PEAR) et de XDebug.

Télécharger et installer le plug-in

Cliquez sur Aide / Installer de nouveaux logiciels

1

Voici la fenêtre qui apparaît à l’écran :

1-bis

Créez un nouveau dépôt pour récupérer les sources du plug-in

Cliquez sur le bouton « Ajouter » (« Add ») et renseignez les champs comme suit :

2

Sélectionnez le paquetage cible

3

Une fois le plug-in installé et Eclipse PDT relancé pour prendre en compte ce nouvel ajout, il va nous falloir paramétrer un projet !

Créer un projet intégrant MakeGood

Créons tout d’abord un projet PHP « local », c’est à dire hébergé sur votre machine. Vous devez avoir un exécutable PHP préalablement installé (PHP cli).

4

Donnons lui un nom trivial, c’est un simple essai, ne cherchons pas les complications inutiles !

5

Voilà maintenant votre projet déployé dans l’explorateur :

6

Ouvrons la vue dédiée à MakeGood :

7

Vous voyez un message d’erreur s’afficher dans cette vue : « MakeGood is not configured yet. Fix… », c’est le signe qu’il nous reste du paramétrage à faire !

8

Paramétrer votre projet PHP pour utiliser MakeGood

L’exécutable PHP

MakeGood aura besoin de localiser votre exécutable PHP, celui que vous utilisez en ligne de commande. Ouvrez donc les préférences générales d’Eclipse PDT :

9

Déployez le noeud intitulé « PHP » et rendez-vous à la sous-section « PHP Executables ». Notez au passage la section sur MakeGood en haut de l’image.

10

Cliquez sur « Add » (« Ajouter ») pour y paramétrer votre executable PHP. A titre d’exemple, voici le mien tel qu’il est configuré sur ma machine GNU/Linux Debian (c’est pourquoi la fenêtre signale « Edit » et pas « Add »).

11

Voici la raison pour laquelle vous devez avoir un PHP Cli installé sur votre machine, c’est une condition sine qua non pour que MakeGood fonctionne, mais ce n’est pas la seule ! Rendez-vous à présent sur le noeud « Debug » de vos préférences PHP :

12

Voici mes paramétrages : nous avons paramétré notre exécutable PHP, autant nous en servir en y faisant référence !

13

PEAR

Il va falloir faire mention de PEAR dans les librairies que devra prendre en charge Eclipse PDT. Pour ce faire, dans les préférences générales de votre IDE, vous vous rendrez à la section « PHP Librairies » :

14

Cliquez sur Nouveau et donnez un nom à votre librairie PEAR…comme PEAR par exemple ! 😉
Une fois que votre librairie apparaît dans la liste, sélectionnez-la et cliquez sur le bouton « Add external folder » pour faire référence au répertoire qui héberge votre installation de PEAR (la racine de tous vos extensions PEAR si vous préférez…), une fois ceci fait, vous devriez avoir quelque chose qui ressemble à peu près à ça à l’écran :

15

Reste à faire mention de votre librairie PEAR cette fois-ci dans les propriétés de votre projet et non plus d’Eclipse PDT. Sélectionnez votre projet dans l’explorateur et sélectionnez les propriétés avec un clic droit.

16

Votre cible est la rubrique « PHP Include Path » où vous allez renseigner la librairie PEAR créée un peu plus haut, mais au niveau Eclipse et pas projet. Cliquez sur « Add Library » ou son équivalent français et sélectionnez ensuite votre bibliothèque PEAR.

17

18

Voici venu le moment de créer un répertoire destiné à contenir nos tests unitaires, faisons preuve d’une imagination sans limites et nommons le « Tests ».
19

20

Toujours dans les propriétés du projet (clic droit ou bien Alt+Entrée, une fois ce projet sélectionné dans l’explorateur), rendez vous dans la partie dédiée à MakeGood et cliquez sur « Add » dans l’onglet « General » pour ajouter votre joli répertoire de test nouvellement créé !

21

22

Désormais, le message d’erreur rouge qui apparaissait tout à l’heure a laissé place à la mention « Waiting a for test run… » dans la vue MakeGood et vous êtes prêt(e) à vous lancer dans le test unitaire :

23

Créer des tests unitaires simples

Notre classe à tester

Créer un fichier PHP à la racine de « MonSuperProjet », nommez le Calculette.php. Dans votre répertoire Tests, créez un fichier PHP nommé « CalculetteTest.php ». Respectez le nommage, dicté par les bonnes pratiques.

24

Vous avez peut-être remarqué ce faisant que, et même si c’est allé très vite, quelque chose s’est passé alors même que vous avez crée ce fichier CalculetteTest.php dans le répertoire Tests…le statut de MakeGood a changé, il a tenté de lancer vos tests mais comme votre fichier est vide, il affiche « Running a test… » avec 0 tests indiqués.

Calculette.php

Ce fichier de test est très librement adapté de l’exemple de la documentation officielle de phpUnit, qu’évidemment je vous encourage à aller voir en ligne ! Voici son contenu :

class Calculette {
    public function RacineCarree($nombre) {

        if (0 > $nombre) {
	    throw new InvalidArgumentException('Le nombre fourni est négatif');
	}

	return sqrt($nombre);
    }
}

CalculetteTest.php

Notre classe de test aura, quant à elle, cette allure là :


require_once 'Calculette.php';

class CalculetteTest extends PHPUnit_Framework_Testcase
{
    protected $_calculette;

    public function setUp() {
	$this->_calculette = new Calculette;
    }

    public function testRacineCarreeNegative() {
	$this->setExpectedException('InvalidArgumentException');
	$this->_calculette->RacineCarree(-1);
    }

    public function testRacineCarreePositive() {
	$this->assertEquals(3, $this->_calculette->RacineCarree(9));
    }
}

N’hésitez pas à ajouter des méthodes dans la classe à tester et évidemment à les tester dans CalculetteTest.php, vous verrez qu’à chaque Ctrl+S dans votre code (dans Calculette.php comme dans CalculetteTest.php), les tests vont se relancer, c’est ça l’idée du « Continuous Test Runner » que se veut être MakeGood. Comme nos tests sont bien écrits, c’est du vert que nous avons; les deux passent !

26

N’oubliez pas d’être curieux(se) et d’aller inspecter les temps d’exécution des tests, vous aurez parfois des surprises !
27

A vous de jouer maintenant !

PHP : classes abstraites et interfaces

Les interfaces, comme les classes abstraites, sont des unités de code dont la raison d’être est de contraindre une classe à implémenter des méthodes, c’est à dire à les concrétiser ! Nous allons voir en détail ces différentes manières de contraindre les comportements de nos classes.
connect

La classe abstraite

Une classe abstraite est avant tout une classe. Rien ne l’oblige à posséder des méthodes abstraites ! Elle peut même être vide :

abstract class EtreVivant {
}

Par contre, dès lors que vous mettez une méthode abstraite dans une classe, vous devez déclarer votre classe comme étant abstraite !

abstract class EtreVivant {
    abstract public function respirer();
}

Seules les fonctions membres de votre classe abstraite peuvent être abstraites, jamais les propriétés ! Une classe abstraite ne s’instancie pas ! Si d’aventure vous tentez d’instancier EtreVivant, voici ce qui va vous arriver :

PHP Fatal error:  Cannot instantiate abstract class EtreVivant

Si ma classe abstraite ne peut pas s’instancier, c’est donc que pour l’utiliser il ne me reste plus qu’une option : la dériver !

abstract class EtreVivant {
    abstract public function respirer();
}

class EtreHumain extends EtreVivant {
}

En l’état actuel de notre hiérarchie d’héritage, une erreur va se produire car EtreVivant nous impose d’écrire respirer(), même vide, ce que nous ne faisons pas !

PHP Fatal error:  Class EtreHumain contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (EtreVivant::respirer)
abstract class EtreVivant {
    abstract public function respirer();
}

class EtreHumain extends EtreVivant {
    public function respirer() {
        //TODO...
    }
}

Une classe abstraite peut forcer les classes qui la dérivent à implémenter des fonctions aux modes d’accès autre que public…protégé par exemple :

abstract class EtreVivant {
    abstract protected function _respirer();
}

class EtreHumain extends EtreVivant {
    protected function _respirer() {
        //TODO...
    }
}

Evidemment, aucun intérêt à mettre une méthode privée ET abstraite dans la classe abstraite…Une méthode privée n’est pas transmise dans les classes filles, elle ne peut être appelée que dans cette classe même, ce serait un grave contre-sens ! Si vous tentiez de le faire, vous seriez bien déçu(e)s :

PHP Fatal error:  Abstract function EtreVivant::_respirer() cannot be declared private

Nous avons dit précédemment qu’une classe abstraite ne pouvait pas être instanciée. Mais cependant, rien ne l’empêche d’avoir un constructeur ! C’est évidemment la classe concrète qui la dérive qui pourra l’utiliser :

abstract class Maman {
    public function __construct() {
        echo "Vous êtes chez Maman";
    }
}

class Fiston extends Maman {}
$fiston = new Fiston;

Quel intérêt ?

Vous utilisez des classes abstraites dès lors qu’un comportement est susceptible de varier selon la classe concrète dans laquelle il se trouve…Regardez notre classe EtreVivant : elle est générique, c’est ce que l’on nomme un super-type ! Sa méthode respirer() est elle aussi bien trop « vague », les êtres vivants respirent de bien des façons différentes ! Nous laissons donc le soin aux classes qui vont nous dériver de donner un corps à cette méthode, pire que ça, même: nous l’imposons !

abstract class EtreVivant {
    abstract public function respirer();
}

class EtreHumain extends EtreVivant {
    public function respirer() {
        echo 'Par le nez et/ou la bouche';
    }
}

class Plante extends EtreVivant {
    public function respirer() {
        echo 'Par la photo-synthèse';
    }
}

Il existe bel et bien une notion de contrainte entre une classe abstraite contenant des méthodes abstraites et sa descendance :

Si tu choisis d’être un EtreVivant, tu dois savoir respirer, peu m’importe comment !

Une classe abstraite peut constituer un début d’implémentation : elle possède des propriétés (un état) factorisables à son niveau et des méthodes (un comportement) qui amorcent une implémentation. Par exemple, nous avons rajouté dans la classe abstraite une méthode mourir qui fait appel à une sous-méthode _cesserFonctionsVitales qui sera implémentée dans les classes dérivées :

abstract class EtreVivant {
    protected $_age = 0;
	protected $_poids = 0;

	public function mourir() {
		$this->_cesserFonctionsVitales();
	}

    abstract public function respirer();
    abstract protected function _cesserFonctionsVitales();
}

class EtreHumain extends EtreVivant {
    protected $_nationalite;

    public function respirer() {
        echo 'Par le nez et/ou la bouche';
    }

    protected function _cesserFonctionsVitales() {
        echo 'le coeur s\'arrête de battre...AAAARGH';
    }
}

class HumainFrancais extends EtreHumain {
    public function __construct() {
        $this->_nationalite = 'français';
    }
}

$francais = new HumainFrancais;
$francais->mourir();

Une classe abstraite dérivant une autre classe abstraite n’est pas forcée d’implémenter les méthodes abstraites de sa mère, vu qu’elle est elle-même abstraite…Par contre la première classe concrète rencontrée dans l’arbre d’héritage en supportera les conséquences ! Ceci va fonctionner car les deux classes sont abstraites (attention aux longues lignées d’héritage et aux trop nombreux niveaux d’abstractions – à partir de 3 – qui sont en général le signe d’une conception de piètre qualité) :

abstract class EtreVivant {
    protected $_age = 0;
	protected $_poids = 0;

	public function mourir() {
		$this->_cesserFonctionsVitales();
	}

    abstract public function respirer();
    abstract protected function _cesserFonctionsVitales();
}

abstract class EtreHumain extends EtreVivant {
}

si vous déplacez la responsabilité de l’implémentation dans la première classe rencontrée dans l’arbre d’héritage, vous obtiendrez le code suivant :

abstract class EtreVivant {
    protected $_age = 0;
	protected $_poids = 0;

	public function mourir() {
		$this->_cesserFonctionsVitales();
	}

    abstract public function respirer();
    abstract protected function _cesserFonctionsVitales();
}

abstract class EtreHumain extends EtreVivant {
    protected $_nationalite;

}

class HumainFrancais extends EtreHumain {

    public function respirer() {
        echo 'Par le nez et/ou la bouche';
    }

    protected function _cesserFonctionsVitales() {
        echo 'le coeur s\'arrête de battre...AAAARGH';
    }

    public function __construct() {
        $this->_nationalite = 'français';
    }
}

$francais = new HumainFrancais;
$francais->mourir();

Mais quelle abomination serait un code pareil ! Tout est fourré à grands coups de pied dans la classe concrète HumainFrancais…Si demain nous devions créer HumainCroate, nous devrions dupliquer bêtement (pléonasme) le code contenu dans cette classe.

L’interface

Les interfaces servent à passer des contrats avec des classes, elles impliquent la même notion de contrainte que les classes abstraites (« Si tu veux être comme moi, tu dois faire comme moi ! »). Mais l’interface est un mécanisme plus simple : ce n’est pas une classe, donc inutile de l’instancier ou d’en hériter ! Puis, même si elle peut contenir des constantes comme dans une classe abstraite (ou pas), les fonctions qu’elle impose sont TOUJOURS en mode d’accès public. Le mot clé pour utiliser une interface est implements.

interface AnimalNageur {
	public function nager();
}

abstract class EtreVivant {
    protected $_age = 0;
	protected $_poids = 0;

	public function mourir() {
		$this->_cesserFonctionsVitales();
	}

    abstract public function respirer();
    abstract protected function _cesserFonctionsVitales();
}

class EtreHumain extends EtreVivant implements AnimalNageur {
    protected $_nationalite;

    public function respirer() {
        echo 'Par le nez et/ou la bouche';
    }

    protected function _cesserFonctionsVitales() {
        echo 'le coeur s\'arrête de battre...AAAARGH';
    }

	public function nager() {
		echo 'Je peux aussi nager si on m\'apprend';
	}
}

class HumainFrancais extends EtreHumain {
    public function __construct() {
        $this->_nationalite = 'français';
    }
}

$francais = new HumainFrancais;
$francais->nager();
$francais->mourir();

Dans cet exemple, la classe EtreHumain implémente l’interface AnimalNageur, car un être humain peut nager (je ne dis pas qu’il sait nager…). Si elle implémente cette interface alors elle doit honorer le contrat qui la lie à présent avec AnimalNageur : donner « vie » à la méthode publique nager.

Une classe peut implémenter plusieurs interfaces :

interface AnimalNageur {
	public function nager();
}

interface AnimalCoureur {
	public function courir();
}

abstract class EtreVivant {
    protected $_age = 0;
	protected $_poids = 0;

	public function mourir() {
		$this->_cesserFonctionsVitales();
	}

    abstract public function respirer();
    abstract protected function _cesserFonctionsVitales();
}

class EtreHumain extends EtreVivant
                 implements AnimalNageur, AnimalCoureur {
    protected $_nationalite;

    public function respirer() {
        echo 'Par le nez et/ou la bouche';
    }

    protected function _cesserFonctionsVitales() {
        echo 'le coeur s\'arrête de battre...AAAARGH';
    }

	public function nager() {
		echo 'Je peux aussi nager si on m\'apprends';
	}

	public function courir() {
		echo 'Je passe une jambe devant l\'autre, très vite';
	}
}

class HumainFrancais extends EtreHumain {
    public function __construct() {
        $this->_nationalite = 'français';
    }
}

$francais = new HumainFrancais;
$francais->nager();
$francais->courir();
$francais->mourir();

EtreVivant implémente dorénavant AnimalNageur et AnimalCoureur, il implémente donc l’ensemble des fonctions imposées par ces deux interfaces (qui ont une méthode chacune).

Les interfaces et l’héritage

Les interfaces peuvent hériter d’autres interfaces et la grande différence par rapport aux classes c’est qu’elles peuvent hériter « en losange » :

interface A {
    public function a();
}

interface B {
    public function b();
}

interface C extends A, B {
    public function c();
}

class D implements C {
    public function a() {
    }
    public function b() {
    }
    public function c() {
    }
}

L’interface C « dérive » les interfaces A et B, c’est à dire qu’une classe qui implémente l’interface C va devoir écrire a(),b() et c().

Faut-il privilégier les interfaces ou les classes abstraites ?

Une fois de plus, il n’y a pas de méthode toute prête de conception, c’est à vous de voir, mais tâchons tout de même de voir les différences fondamentales :

  • une classe abstraite oblige à la dériver pour bénéficier de ses fonctionnalités: les méthodes « enfermées » dans une classe abstraite forcent l’héritage, ce qui n’est pas nécessairement bon
  • les méthodes « sorties » dans une ou des interfaces favorisent la ré-utilisabilité (si on enferme la méthode courir dans EtreHumain, seuls les êtres humains pourront courir…Si nous écrivons une interface AnimalCoureur avec cette méthode, une autruche pourra l’implémenter et courir à son tour, sans aboutir à l’ineptie qui consisterait pour Autruche à dériver EtreHumain pour pouvoir courir)

Résumé

  • Une classe abstraite peut contenir du code, pas une interface. Vue sous cet angle,
    une interface est 100% abstraite, elle ne contient que des prototypes (ou signatures)
    de fonctions publiques
  • Une interface, comme une classe, peut posséder des constantes
  • Une interface s’implémente, une classe s’instancie ou se dérive
  • Une classe peut implémenter plusieurs interfaces mais ne peut spécialiser qu’une seule classe (en PHP)
  • Une interface peut être implémentée par plusieurs classes
  • Les méthodes d’une interface sont forcément publiques, celles d’une classe abstraite peuvent être de tout type, comme dans une classe normale et uniquement publiques ou protégées si elles sont abstraites

PHP: echo vs print

Ces derniers temps en me baladant nonchalamment sur le Web, j’ai eu l’occasion de voir à de nombreuses reprises des « matches » entre echo et print, certains soutenant (fort heureusement, avec leurs benchmarks à l’appui) qu’echo était plus rapide que print. Nous allons voir ce qu’il en est de notre côté !

Echo et print

Echo n’est pas une fonction ! C’est officiellement une « structure du langage » (language construct) PHP.

Echo, lorsqu’on l’utilise avec des parenthèses, ne prend qu’un seul paramètre :

$prenom = 'Sebastien';
// avec des parenthèses
echo ("Bonjour, " . $prenom);

Notre unique paramètre ici est le résultat de la concaténation de la chaîne de caractères « Bonjour,  » avec la variable $prenom qui contient la valeur Sebastien.

Les virgules nous permettent également le passage de plusieurs paramètres à echo :

$prenom = 'Sebastien';
echo 'Bonjour, ', $prenom, ' !';

Faisons un savant mélange de tout ça :

$prenom = 'Sebastien';
echo "Salut, " . ($prenom . " Ferrandez"), ", ça va ?";

Print n’est pas non plus une fonction, et ce même si print retourne une valeur entière (constante, toujours égale à 1). Print est une aussi construction du langage plus proche de la fonction qu’echo puisqu’elle renvoie une valeur mais la comparaison s’arrête là.

C’est parfaitement inutile, mais de fait rien ne vous interdit de faire le branchement conditionnel suivant :

$prenom = 'Sebastien';
if (1 === print("Bonjour")) {
    echo ",$prenom";
}

ou bien, puisque print renvoie 1 et que 1 == true avec l’égalité dite classique :

$prenom = 'Sebastien';
if (print("Bonjour")) {
    echo ",$prenom";
}

Elephant-PHP

Un benchmark maison

Pour valider la prévalence de l’un de ces language constructs sur l’autre, rien ne vaut un test ! Je suis horripilé par les gens qui propagent des légendes urbaines du développement sans apporter la preuve de ce qu’ils répètent bêtement (« telle fonction est plus rapide que telle autre », « telle chose c’est le Mal ! », « telle autre chose n’est pas optimisée ») !

Voici le programme trivial qui nous sert pour nos tests :

$time_start = microtime(true);

for ($i=0; $i<10000; $i++) {
   print "test";
}

$time_end = microtime(true);
$time = $time_end - $time_start;

echo PHP_EOL . "$time secondes\n";

Evidemment, pour tester echo, vous prendrez soin de remplacer l’appel à print par echo !
Voici la version de php CLI que j’ai utilisé pour mener ces petites investigations sous GNU/Linux Debian sid :

PHP 5.4.4-14 (cli) (built: Mar  4 2013 14:08:43) 
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2012 Zend Technologies

L’idée ici était de faire des boucles avec un nombre d’itérations décuplé. Voici les résultats de ces expérimentations :

100 impressions du mot test :
echo : 0.00029110908508301 secondes
print : 0.00029802322387695 secondes

1000 impressions du mot test :
echo : 0.0025129318237305 secondes
print : 0.0025389194488525 secondes

10000 impressions du mot test :
echo : 0.025437831878662 secondes
print : 0.027919054031372 secondes

100000 impressions du mot test :
echo : 0.30710697174072 secondes
print : 0.23741006851196 secondes

1 million d’impressions du mot test :
echo : 2.6142749786377 secondes
print : 2.5098519325256 secondes

10 millions d’impressions du mot test :
echo : 27.915874958038 secondes
print : 33.695736885071 secondes (quasiment 5 secondes de plus !)

Ces valeurs sont des valeurs moyennes, calculées sur l’ensemble des 5 tests lancés dans chacun des cas et pour chaque language construct. Ce qu’on peut observer à la lumière de ces tests empiriques c’est qu’effectivement, echo est plus rapide en terme de temps d’exécution que print. Cependant, la tendance s’inverse dans certains cas, notamment ici lorsque le nombre d’itérations croît pour atteindre un million d’itérations. La tendance initiale se retrouve sur des nombres très élevés d’itérations (ici, 10 millions).

En conclusion

Nous avons pu vérifier avec quelques exemples concrets que oui, echo est plus rapide que print. Evidemment, nous le voyons d’autant plus que nous poussons ces deux language constructs dans leurs derniers retranchements, avec un nombre d’itérations épouvantable (qui fait ça en pratique, hein ?).

J’ai tendance à penser que lorsqu’on en est arrivé à tenter de gagner des millisecondes sur un appel à une fonction ou un language construct PHP, c’est qu’on n’a plus rien à faire au niveau de la qualité du code produit et malheureusement, l’expérience m’a souvent montré qu’avant de s’attacher à optimiser des appels comme ceux-ci, les développeurs feraient mieux de s’attacher à produire du code de qualité (conforme aux principes SOLID, par exemple) !

Quelques liens

Documentation PHP : echo
PHP Benchmark http://www.phpbench.com/ [anglais]

PHP : quelques code smells

Cette expression code smell a été inventée par Kent Beck et elle est apparue pour la première fois dans le livre « Refactoring: Improving the Design of Existing » de Martin Fowler; signifiant littéralement « odeurs de code », cette expression peut être vue comme matérialisant une « piste olfactive » qui vous indique – à vous, fins limiers du code – que quelque chose ne va pas dans des développements (les vôtres ou des développements hérités – legacy). Voici quelques unes de ces « odeurs » que l’on rencontre le plus fréquemment; j’ai parfois utilisé des noms « officiels » tout comme des noms que j’emploie personnellement pour les désigner au quotidien.

Les commentaires : les commentaires expliquent brièvement ce qui se passe dans le code. Pour reprendre l’adage populaire : « Trop de commentaire tue le commentaire », soyez concis, ne détaillez pas le code trop finement, sinon vous paraphrasez !

Le code dupliqué : il indique un mauvais design. C’est un signe qu’il est temps de refactorer.

La forêt de « if » : si vous noyez votre code dans une jungle de branchements conditionnels, c’est que vous faites fausse route. Refactorez là encore en utilisant des designs patterns tels que Strategy, par exemple.

La « monstro-classe » : typique du mauvais design, du non respect des principes SOLID et en particulier du premier, dit de la Single Responsibility. Ces classes font tout, le café, le service, le parking…Un jour ou l’autre vous pairez cher ces 3000 lignes d’inepties.

La syllogomanie : on conserve du code mort dans de gros pavés de commentaires…sans savoir pourquoi… »au cas où ». Et les systèmes de gestion de version, ça sert à quoi ?

La foire aux variables d’instances : on en crée plein, pour y stocker des valeurs qui ne servent que temporairement à l’objet, qui du coup enfle et enfle et enfle.

La classe fantôme : elle n’a pas vraiment d’utilité, de raison d’être, elle ne fait rien de significatif ou bien délègue l’intégralité de ses tâches. Elle doit être impitoyablement éliminée du design.

Le nommage inapproprié : s’applique aux variables ou aux méthodes; des exemples vu dans les entreprises pour lesquelles j’ai travaillé : des méthodes nommées « find2 », « find3 » et qui faisaient presque la même chose que la méthode originelle « find » (et les Design Patterns, ça sert à quoi ?), des variables nommées $jb, du nom de leur créateur, ou $a, $b, $o, des méthodes avec des noms horriblement longs et ridicules, comme « faitCeQuilFautPourLeTac() » (tout ça est vrai, je n’invente rien !).

L’absence de typage : PHP est faiblement typé comme vous le savez, mais vous pouvez au moins typer les paramètres d’appel des fonctions (objets/interfaces/tableaux). Usez en et abusez en pour verrouiller votre conception objet et ne pas permettre tout et n’importe quoi !

Pour aller plus loin :

Smells to refactoring (PHP, anglais)

Comment écrire du code inmaintenable (anglais)

Code smells (anglais)

smell

Image empruntée au site Rin’s treasure

PHP objet : le late static binding en 5 minutes chrono

913_always_late_car_decal__04163

Top, c’est parti ! Tout d’abord, qu’est-ce que le late static binding (résolution statique à la volée), me direz-vous ? C’est un mécanisme qui, dans un contexte d’appel statique, permet de faire référence à la classe courante (la « vraie » classe). Voici comment nous faisions (avant PHP 5.3) dans un contexte d’héritage statique (A a une méthode statique dont B hérite tout en l’écrasant – même si elle est identique à l’originale) :


class A {
    public function pointDentree() {
        self::methode();
    }

    public static function methode() {
        echo __CLASS__, PHP_EOL;
    }
}

class B extends A {
    public static function methode() {
        echo __CLASS__, PHP_EOL;
    }
}

$b = new B;
$b->pointDentree();

Nous instancions un objet de la classe B, nous nous attendons donc à avoir « B » affiché à l’écran or c’est A qui s’affiche, signe que la résolution statique ne se fait pas au runtime (exécution) mais à la définition de la classe (compilation). Pour obtenir le comportement attendu, il suffit de faire usage du mot clé static :


class A {
    public function pointDentree() {
        static::methode();
    }

    public static function methode() {
        echo __CLASS__, PHP_EOL;
    }
}

class B extends A {
    public static function methode() {
        echo __CLASS__, PHP_EOL;
    }
}

$b = new B;
$b->pointDentree();

Ici j’ai bien B qui s’affiche ! PHP nous donne les raisons de la limitation du mot clé self :

Les références statiques à la classe courante, avec self:: ou __CLASS__, sont résolues en utilisant la classe à laquelle appartiennent les fonctions, celle où elles ont été définies (source : http://php.net/manual/fr/language.oop5.late-static-bindings.php)

Utilisez donc à bon escient ce mécanisme de late static binding dans vos développements, un effet de bord est si vite arrivé quand on fait de l’héritage statique !

PHP : le registre d’autoload de la SPL

Pour bénéficier de l’autoloading (ou chargement automatique), on peut utiliser la fonction built-in __autoload ou bien la pile (ou registre) d’autoload. Je privilégie davantage la seconde, parce qu’une classe est pour moi une unité de programme plus structurée (et plus structurante !) qu’une simple fonction jetée à la hâte dans le code. Voici un exemple très simple d’utilisation de la pile de chargement automatique des classes.

class Autoload
{
    private static $_classDir = './classes/';

    public static function classesAutoloader($class)
    {
        $path = static::$_classDir . "$class.php";

        if (file_exists($path) && is_readable($path)) {
            require $path;
        }
    }
}

spl_autoload_register('autoload::classesAutoloader');
// hop !, la méthode statique est placée dans la pile !

Ici nous définissons une classe avec une fonction statique chargée de donner à PHP les moyens d’aller trouver les fichiers contenant les définitions de classes. Le nom de la classe à trouver est injecté par le langage dans les fonctions de chargement automatique: c’est cette variable $class qui va nous servir à construire un chemin vers le fichier qui contient la définition de la classe.

Une fois notre méthode implémentée, nous la plaçons dans la pile d’autoloading grâce à un appel à spl_autoload_register (notez bien l’insensibilité à la casse) et le tour est joué. Evidemment, vous empilerez autant de méthodes que souhaité même si cet exemple, qui se veut simplissime, n’en contient qu’une (attention toutefois à la surenchère…).

L’auto-loading est devenue une pratique courante depuis la sortie de PHP 5 et plus encore depuis l’avènement des frameworks MVC en PHP, il est indispensable en 2013 de comprendre en quoi consiste ce mécanisme !

Pour plus d’info : la documentation PHP

PHP : un exemple simple du design pattern Template Method

Vous cherchiez un design pattern facile à aborder ? Le design pattern template method est celui qu’il vous faut ! Son principe est très simple : dans une classe, une méthode dite template est composée de sous-méthodes dont on sait que chaque sous-classe l’implémentera à sa manière. Ces sous-méthodes sont généralement en type d’accès protégé car invoquées uniquement par cette fameuse méthode template; l’extérieur n’a pas à connaître les mystères de votre implémentation (encapsulation, vous dîtes ?). Bien entendu, étant donné que chaque classe fille implémentera ces méthodes comme bon lui semble, il convient de les signifier comme abstraites dans la classe mère.

Imaginons une classe TunnelCommande qui expose une méthode template nommée finaliserCommande; cette méthode décrit un algorithme en spécifiant ce qui devra être fait par ses sous-classes et dans quel ordre. Cette classe comporte la méthode payePort dite « adaptateur » (hook) qui peut être réécrite dans les classes filles. Elle sert à conditionner une partie du flot d’exécution de l’algorithme de la méthode template. Son utilité dans notre cas est de permettre à un certain type d’utilisateur de s’affranchir du paiement des frais de port.

abstract class TunnelCommande
{
    public function finaliserCommande(): void
    {
        $this->faireTotal();
        
        if ($this->payePort()) {
            $this->ajouterFraisPort();
        }

        $this->rediriger('page_paiement');
    }

    public function payePort(): bool
    {
    	return true;
    }

    public function rediriger(string $template): void
    {
    	echo "Redirection vers ", $template, PHP_EOL;
    }

    abstract protected function faireTotal(): void;
    abstract protected function ajouterFraisPort(): void;
}

Nous avons deux classes concrètes qui dérivent TunnelCommande et implémentent les méthodes abstraites en leur donnant un comportement spécifique. Dans CommandePremium, la méthode ajouterFraisPort qui est imposée par la classe mère abstraite ne fait rien, payePort renvoyant false elle ne sera de toutes façons jamais invoquée dans ce scénario là.


class CommandeClient extends TunnelCommande
{
    protected function faireTotal(): void
    {
        echo "Je fais le total", PHP_EOL;
    }

    protected function ajouterFraisPort(): void 
    {
        echo "J'applique les frais de port du client normal", PHP_EOL;
    }
}

class CommandePremium extends TunnelCommande
{
    protected function faireTotal(): void
    {
        echo "Appliquer 5% de rabais pour les clients Premium", PHP_EOL;
    }

    protected function ajouterFraisPort(): void
    {
        return;
    }

    public function payePort(): bool
    {
    	return false;
    }
}

$premium = new CommandePremium;
$premium->finaliserCommande();

$standard = new CommandeClient;
$standard->finaliserCommande();

Dans ce design pattern, tout le travail est fait dans la classe mère, abstraite. Quand je dis « tout le travail », je parle de la structure générale de l’algorithme, de l’ordre des opérations. Evidemment, la responsabilité de l’implémentation des détails de cet algorithme « général » est déléguée aux classes dérivées, via le mécanisme d’abstraction.