Archives pour la catégorie Software

Ruby On Rails – utiliser first_or_create

Lorsque vous utilisez ActiveRecord, vous vous intéressez forcément tôt ou tard à la fonction first_or_create et en général, vous l’utiliser comme suit:


MonJoliModele.first_or_create(attribut: "unevaleur")

Ce qu’on pense faire…

En procédant ainsi, vous pensez « Soit un enregistrement avec comme valeur du champ attribut « unevaleur » est trouvé, soit je vais en créer un ». Et bien non ! Ouvrez-vous un rails console et regardez ce qui se passe en réalité. Et oui, Rails prend le premier enregistrement de la table liée au modèle, sans même tenir compte de la valeur du champ « attribut » que vous lui donnez, pensant logiquement qu’il allait en faire quelque chose…Évidemment si votre table est vide alors Rails créera cet enregistrement avec la valeur passée en paramètre, mais c’est loin d’être tout le temps le cas.

Développeur sceptique: ne me dites pas que vous n'avez jamais fait cette tête, vous mentiriez !

Développeur sceptique: ne me dites pas que vous n’avez jamais fait cette tête, vous mentiriez !


Ce que vous vouliez faire en réalité, c’était:

MonJoliModele.where(attribut: "unevaleur").first_or_create(attribut: "unevaleur")

ou, plus élégant :

critere = {attribut: "unevaleur"}
MonJoliModele.where(critere).first_or_create(critere)

Attention donc aux effets de bord possiblement dus à la mauvaise utilisation de cette fonction. Notez qu’on peut utiliser un bloc aussi. Mais attention, il n’est exécuté qu’en cas de création d’un nouvel enregistrement et vous l’utiliserez évidemment si vous avez de nombreuses affectations à y faire, et pas forcément deux comme dans mon exemple:


critere = {attribut: "unevaleur"}
MonJoliModele.where(critere).first_or_create do |modele|
modele.autreattribut = "une autre valeur"
modele.encoreunautreattribut = "encore une autre valeur"
end

Voilà donc pour ce crash course sur l’utilisation de first_or_create, fonction dont le nom assez ambigu peut être source de confusion et qui en même temps nous épargne d’avoir à faire un find_by suivi d’un create, ce qui n’est ni très esthétique, ni très performant !

Hors micro: le télétravail

Un petit billet rapide pour démarrer cette nouvelle année (la troisième pour ce blog qui a essentiellement vocation à parler technique, vous l’aurez remarqué) avec un sujet qui me tient à cœur: le télétravail dans les NTIC en France.

Pourquoi le télétravail, qui aide à moderniser l’organisation sans toutefois trop la bousculer, est-il tellement à la traîne en France ? Les entreprises du monde des NTIC semblent toujours plus favorables à ce mode de collaboration – c’est en tous les cas ce qui ressort de chacun des sondages d’opinion faits sur le sujet tous les ans – mais dans les faits elles restent réticentes pour appliquer le changement…Qu’est-ce qui ne va pas avec le télétravail en France ? Est-il encore mal perçu en 2016 ? N’est-il pas si soluble que ça dans la culture de l’entreprise française ?

Take the turn !

Take the turn !

J’ai eu la chance de tester ce mode de collaboration il y a presque 10 ans, à la suite de la naissance de mon deuxième enfant. Mon manager de l’époque, très compréhensif, m’avait accordé de travailler 2 jours par semaine à domicile et ce alors que je résidais à 10 km de mon lieu de travail (comme quoi ce n’est pas une histoire de distance – length does NOT matter). Ce mode de fonctionnement a duré trois ans, jusqu’à mon départ de l’entreprise. Certes, il a fallu prouver ma fiabilité et mon sérieux dans le travail avant de me voir accorder ce que je considère encore à ce jour comme un énorme avantage dans la gestion personnelle du travail.

Car oui, de mon point de vue, le télétravail est beaucoup moins stressant et beaucoup plus productif que dans les locaux de l’entreprise:

  • vous n’êtes pas dérangés par le téléphone qui sonne toutes les 30 secondes ou les gens qui hurlent sans cesse à côté de vous (je m’adresse aux gens qui travaillent en open space)
  • vous n’êtes pas sollicités par vos collègues pour partir en pause chaque heure (celui de la compta, puis celui de l’info, puis celui des achats etc.)
  • vous n’êtes pas conviés à des réunions interminables où vous n’aviez pas votre place (la preuve, vous avez rempli une page de votre cahier de dessins aléatoires, ce cahier que vous emportez à chaque fois alors que vous n’y notez rien…et vous le savez pertinemment !)
  • Pour résumer : plus de concentration et de calme = plus de travail de qualité !
Pas du tout ma vision du travail

Pas du tout ma vision du travail

Les aspects pratiques pèsent aussi dans la balance:

  • Fini le temps perdu dans les embouteillages, une douche et c’est parti, vous êtes devant votre poste de combat à 8 h 30 au lieu des 9 heures et quelques (vous êtes de toutes les manières incapables de savoir quand vous arriverez, étant à la merci de la circulation, des grèves, des travaux parfois des caprices de votre véhicule).
  • Il ne vous faut rien ou presque pour bosser : un ordinateur et une connexion SSH.
  • La communication se fait par d’autres canaux, qui forcent parfois les gens à réfléchir avant de vous déranger pour rien; le mail force les gens à savoir formuler leur besoin de manière compréhensible, claire et précise. De plus n’importe qui possédant une ligne téléphonique peut suivre un conf call depuis chez lui.
  • Certes, tout n’est pas rose :

    Il faut savoir avant tout gérer le fait d’être seul, mentalement et techniquement (ça force à être autonome). Il faut aimer être à distance et il faut pouvoir se dépatouiller en toute occasion. J’ai vu des gens s’isoler complètement dans leur coin, au point de décrocher totalement et de ne plus assurer leurs tâches (ce qui a, hélas!, entrainé leur éviction), mais pour un échec cuisant, combien de salariés ou de collaborateurs y trouvent leur compte et une certaine forme d’épanouissement ? Le télétravail influe sur le moral et sur la qualité de l’ouvrage, à condition bien sûr de se sentir bien chez soi. Si l’environnement est bruyant et pas propice à la sérénité, c’est évidemment très dur, mais un collaborateur à qui vous accordez cette confiance prendra ça comme une preuve de confiance voire un avantage en nature !

    Car oui, la confiance reste encore la principale entrave au développement de ce mode de collaboration dans notre bon vieux pays. On reste dans un schéma sclérosé où la place du collaborateur est sous les yeux du manager et nulle part ailleurs. On part du principe que le collaborateur pourrait être distrait chez lui alors que ça ne dérange personne que certains passent leur temps de travail sur les réseaux sociaux ou sur leur téléphone portable quand les accès au Web sont filtrés.
    « Du moment qu’ils sont au bureau et font leurs horaires… ». Qu’est-ce qui est important ? Faire ses horaires ou accomplir ses tâches ? La rigidité ou la flexibilité ? Le sentiment qu’on nous fait confiance ou qu’on est pris pour un garnement qu’il faut constamment surveiller ? On passe 45 ans de sa vie à travailler, autant aimer son travail et s’y impliquer, non ?

    Cela fait trois ans maintenant que je suis indépendant, travaillant uniquement depuis chez moi, à l’exception évidemment des formations, que je tiens à assurer en présentiel. Je ne donnerais ma place pour rien au monde : j’aime encore mon métier après 18 ans passés à l’exercer et j’aime le fait de rester dans mon environnement pour travailler : je ne touche plus ma voiture que quelques fois par semaine pour des trajets courts et croyez-moi, je ne m’en porte que mieux. J’entends encore des gens me dire « Je ne sais pas comment tu fais, je ne pourrais pas travailler seul » ou « Je serais trop tenté de faire autre chose que travailler ». Je ne tente pas de les convaincre qu’il s’agit de la panacée – on s’en sent capable ou pas – mais travailler depuis chez moi est un choix personnel que j’estime payant et j’encourage vivement entreprises des NTIC et collaborateurs désireux de briser la routine ou hésitants à tenter l’expérience.

    Sus à la frilosité ! Le télétravail, c’est génial !

    Si vous avez des retours d’expérience (identiques ou différents !), n’hésitez pas à les évoquer dans les commentaires

    PHP 7 : le typage strict

    Une des grandes forces de PHP – qui est aussi, hélas !, une de ses plus grandes faiblesses – est l’absence de typage fort comme c’est le cas en Java, en C#, C++ ou dans bien d’autres langages dits « orientés objet ». PHP s’occupe de faire les conversions de type qui l’arrangent, notamment lorsque l’on effectue des comparaisons entre des types différents (voilà pourquoi les opérateurs stricts ont été introduits qui comparent en valeur et surtout en type). Tout ça est décidé de façon dynamique au runtime (à l’exécution, en bon français) et non pas de façon statique, à la compilation.

    Une nouveauté qui fait des heureuses !

    Une nouveauté qui fait des heureuses !

    Depuis les versions 5.X, on pouvait déjà typer des paramètres de fonction avec certains types non-scalaires : objet depuis 5.0 et array depuis 5.1 (et callable par la suite, mais passons…). Dans des temps reculés (avant 5.X), on n’avait pas trop le choix:
    function peuimporte ($param, $reparam) {
    var_dump($param);
    var_dump($reparam);
    }

    L’appel de cette fonction pouvait se faire avec des arguments de divers types : entiers, flottants, tableaux, objets, chaînes de caractères…Pour faire court, on pouvait un peu passer tout et (surtout) n’importe quoi !

    peuimporte (1, 1.23);
    peuimporte ("a", 1);
    peuimporte (array(), new StdClass());

    Un type scalaire est un type qui ne contient qu’une valeur à la fois: un entier, une chaine de caractères, un booléen etc. Un type non scalaire peut contenir plusieurs valeurs; un tableau, un objet…ce sont des types composites – ou des collections de valeurs.

    Depuis PHP 5.1 disais-je, on pouvait forcer le typage des paramètres des fonctions avec des types non scalaires comme un tableau:


    function peuimporte (Array $param) {
    // faites ce que bon vous semble
    }

    ou bien un objet d’un type particulier:


    class UnTypeAuPif {}
    function peuimporte (UnTypeAuPif $param) {
    // faites ce que bon vous semble
    }

    ou encore d’un super-type, comme une interface:


    interface Bidon {}
    class UnTypeAuPif implements Bidon {}
    class UnAutreTypeAuPif implements Bidon {}
    function peuimporte (Bidon $param) {
    // faites ce que bon vous semble
    }

    Il suffit alors de passer en argument une instance qui ne se conforme pas aux spécifications que constitue la signature de notre fonction et VLAN!


    Catchable fatal error: Argument 1 passed to peuimporte() must be an instance of UnTypeAuPif, instance of stdClass given

    Ici j’ai tenté de réaliser l’invocation suivante :
    peuimporte(new StdClass);

    J’ai allégrement violé le contrat qui me lie à cette fonction et qui stipule qu’il ne faut passer que des instances de UnTypeAuPif (dans le premier exemple).

    Depuis mars 2015 (et une RFC qui a fait l’objet d’un débat, puis d’un vote) il est donc prévu que PHP 7 nous donne la possibilité de spécifier des types scalaires pour nos arguments…ENFIN ! Il deviendra donc possible d’écrire:


    function peuimporte (int $param) {
    // faites ce que bon vous semble
    }

    ou bien:


    function peuimporte (string $param) {
    // faites ce que bon vous semble
    }

    ou encore:


    function peuimporte (float $param) {
    // faites ce que bon vous semble
    }

    Le fait de passer en argument d’un appel une variable du mauvais type provoque une erreur fatale, par exemple:


    Fatal error: Uncaught TypeError: Argument 1 passed to peuimporte() must be of the type float, string given

    Pour activer ce typage strict sous PHP 7, il faut utiliser declare que l’on connait depuis 5.3 et qui sert lors de la compilation du fichier. Ce language construct sera placé en tout début de fichier (sous peine de provoquer une erreur fatale)  comme suit:


    declare(strict_types = 1);
    function peuimporte (float $param) {
    // TODO
    }
    peuimporte(1.23);

    Notez que le mode bloc, originellement proposé pour strict_types , a été interdit dans PHP 7. Cette fonctionnalité n’est pas forcée par défaut, nul risque donc de BC break dans le code existant. L’aspect lâche du typage en PHP reste encore la règle (pour combien de temps ?), ce qui continuera de faciliter l’intégration des gens qui ne sont pas des développeurs de formation.

    Pour les développeurs qui viennent du monde des langages fortement typés et qui se sentent frustrés par le typage faible de PHP, c’est une bonne nouvelle et sans doute une raison de plus de cesser de prendre PHP pour un langage de seconde zone.

    Il me tarde que PHP 7 fasse l’objet d’une release officielle pour bénéficier de cette tant attendue fonctionnalité !

    Testé avec un PHP7 bêta compilé sur Debian Jessie…et approuvé !

    PHP : le design pattern Proxy

    Proxy, proxy…proxy de cache, proxy Web…vous avez sans doute déjà lu ce terme quelque part, n’est-ce pas ? Si oui, alors vous avez déjà sa principale raison d’être en tête : un proxy s’intercale entre vous et…quelque chose !

    En programmation, ce quelque chose est un objet « distant »…distant parce qu’il peut se trouver ailleurs sur le réseau mais pas seulement ! Il peut très bien se trouver sur la même machine mais dans un autre espace d’adressage. En pratique, le proxy implémente la même interface que l’objet auquel il sert d’écran, car il va se substituer à lui !

    Ce design pattern fait état de relations entre des objets, voilà pourquoi on dit qu’il est structurel ! Proxy (ou Procuration ou encore Surrogate en anglais) a des similitudes avec un autre pattern structurel : Décorateur. Cependant, il convient de bien garder à l’esprit que si Décorateur a pour but d’ajouter des fonctionnalités à l’objet décoré, Proxy est souvent là pour effectuer un contrôle d’accès à un objet.

    Si vous avez déjà travaillé avec le framework Symfony et l’ORM Doctrine, cette notion de proxy ne vous est pas inconnue car vous manipulez des proxies en bien des occasions !

    Gardes du corps peu enclins à la discussion

    Les proxies du président de la République ! Pour émettre une requête à ce dernier, c’est par eux qu’il faudra passer !

    L’interprète, un mandataire idéal !

    Voilà un nouveau terme français pour notre Proxy : mandataire. Quel meilleur exemple que celui de l’interprète, par lequel il est impératif de passer lors des sommets internationaux, si l’on veut être compris des grands de ce monde (et éventuellement lui faire porter la responsabilité d’un incident diplomatique) ! Voilà un exemple très simple pour illustrer ça : des interfaces que le sujet réel (le président russe) et son proxy (l’interprète russe-français) implémentent tous deux et un président hôte qui prend en composition un interprète avec lequel il va parler tandis que celui-ci va de son côté discuter avec le sujet réel. Dans cet exemple, j’en profite pour utiliser la dérivation d’interfaces, le mot clé final, une classe abstraite…En réalité j’aurais largement pu simplifier le code, j’aurais pu aussi injecter directement l’interprète dans le constructeur du président hôte, qui mène les conversations, au lieu de mettre un setter dont je ne suis pas spécialement adepte, bref j’aurais pu faire des tas de choses autrement mais je veux garder le code efficace et amusant autant que possible !

    interface PersonneInterface
    {
        public function parlerDuTemps(): string;
    }
    
    interface PresidentInterface extends PersonneInterface
    {
        public function parlerDuRechauffementClimatique(): string;
        public function parlerDesGuerres(): string;
    }
     
    final class PresidentRusse implements PresidentInterface
    {
        public function parlerDuTemps(): string
        {
            return 'Я очень рад быть здесь, погода прекрасная в Париже';
        }
    
        public function parlerDuRechauffementClimatique(): string
        {
            return 'это очень серьезная проблема !';
        }
    
        public function parlerDesGuerres(): string
        {
            return 'какая война?';
        }
    }
    
    interface InterpreteInterface
    {
        public function boireUnVerreEau(): string;
    }
    
    abstract class Interprete implements InterpreteInterface
    {
        protected $presidentHote;
        
        protected $presidentInvite;
    
        public function __construct(PresidentInterface $presidentHote,
                                  PresidentInterface $presidentInvite)
        {
            $this->presidentHote = $presidentHote;
            $this->presidentInvite = $presidentInvite;
        }
        
        public function boireUnVerreEau(): string
        {
            return 'Glou Glou Glou'.PHP_EOL;
        }
    }
     
    final class InterpreteRusse extends Interprete implements PresidentInterface
    {
        public function parlerDuTemps(): string
        {
            $temps = 'Au sujet du temps, le président russe me dit : "';
            $temps .= $this->presidentInvite->parlerDuTemps().'"'.PHP_EOL;
            
            return $temps;
        }
    
        public function parlerDuRechauffementClimatique(): string
        {
            $climat = 'Au sujet du climat, le président russe me dit : "';
            $climat .= $this->presidentInvite->parlerDuRechauffementClimatique().'"';
            
            return $climat.PHP_EOL;
        }
    
        public function parlerDesGuerres(): string
        {
            if ($this->presidentHote instanceof PresidentFrancais) {
                return 'Le président russe ne souhaite pas évoquer le sujet 
                        avec le président français ! Prenons plutôt Vodka !'.PHP_EOL;
            }
            
            $guerre = 'Au sujet des guerres, le président russe me dit : "';
            $guerre .= $this->presidentInvite->parlerDesGuerres().'"'.PHP_EOL;
            
            return $guerre;
        }
    }
     
    final class PresidentFrancais implements PresidentInterface
    {
        private $interprete;
         
        public function attacherInterprete(InterpreteInterface $interprete): void
        {
            $this->interprete = $interprete;
        }
        
        public function parlerDuTemps(): string
        {
            return 'Président, il fait bon vivre à Paris, n'est-ce pas ?'.PHP_EOL;
        }
    
        public function parlerDuRechauffementClimatique(): string
        {
            return 'Que pensez-vous du réchauffement climatique ?'.PHP_EOL;
        }
    
        public function parlerDesGuerres(): string
        {
            return 'Que vous inspirent les conflits mondiaux ?'.PHP_EOL;
        }
        
        public function discuterSurPerron(): void
        {
            if (!$this->interprete) {
                throw new RuntimeException('Où est l\'interprète ?');
            }
             
            echo $this->parlerDuTemps();
            echo $this->interprete->parlerDuTemps();
            
            echo $this->parlerDuRechauffementClimatique();
            echo $this->interprete->parlerDuRechauffementClimatique();
            
            echo $this->parlerDesGuerres();
            echo $this->interprete->parlerDesGuerres();
        }
    }
     
    $presidentFrancais = new PresidentFrancais();
    $presidentRusse = new PresidentRusse();
    $InterpreteRusse = new InterpreteRusse($presidentFrancais, $presidentRusse);
    
    $presidentFrancais->attacherInterprete($InterpreteRusse);
     
    try {
        $presidentFrancais->discuterSurPerron();
    } catch (RuntimeException $exception) {
        echo "Allô, ici le chef du protocole !", PHP_EOL;
        echo "Le président vient de me dire '" . 
                  $exception->getMessage(), "'", PHP_EOL;
        echo "Vite, allez chercher un interprète !", PHP_EOL;
    }
    
    echo $InterpreteRusse->boireUnVerreEau();
    

    Une fois de plus, voilà un design pattern fait la part belle à l’abstraction !

    Vous notez ici que la tentation serait grande pour notre interprète de proposer des fonctionnalités que ne propose pas le président dont il assure la traduction. En faisant ainsi, nous nous éloignerions de Proxy – dont le rôle consiste majoritairement à faire de la délégation – pour adopter une approche Décorateur.

    Notre interprète russe joue également le rôle de proxy de protection en filtrant les accès au président russe sur des questions épineuses: en l’occurrence, le président russe n’a pas tellement envie de parler des guerres avec son homologue français et l’interprète a reçu lors de son briefing des instructions sans équivoque…heureusement qu’en bon russe, il a prévu un verre de vodka pour désamorcer tout début de crise diplomatique ! Faire des vérifications de droit d’accès, voilà aussi un autre aspect de ce design pattern.

    Le chargement fainéant

    Personne n’emploie ce terme en réalité, mais il me fait rire ! Le lazy loading consiste à différer le chargement du vrai objet (celui qui nous mandate, qui nous donne procuration) jusqu’au moment où l’on s’en servira effectivement ! Ceci est notamment utile lorsqu’un objet est un plutôt gourmand en ressources.

    Prenons l’exemple d’une classe qui sert habituellement à manipuler des images. Lorsqu’on l’instancie, elle range dans une variable d’instance prévue à cette effet la totalité du flux de l’image qu’on lui passe en paramètre.

    Notre proxy, qui possède le même super-type que la classe image (une classe abstraite implémentant une interface) ne va pas effectuer ce chargement mais il va attendre le dernier moment pour instancier la classe qui le mandate et appeler dessus les méthodes demandées par le client :

    interface ImageInterface
    {
        public function donnerTaille(): int;
        public function aContenu(): bool;
        public function afficherContenu(): ?string;
    }
    
    abstract class AbstractImage implements ImageInterface
    {
    	protected $cheminFichier;
    	protected $contenuFichier;
    	protected $tailleFichier;
    	
    	public function __construct($cheminFichier)
    	{
    		$this->cheminFichier = $cheminFichier;
    	}
    	
    	public function donnerTaille(): int
    	{
    		return $this->tailleFichier;
    	}
    	
    	public function aContenu(): bool
    	{
    		return null !== $this->contenuFichier;
    	}
    }
    
    class StandardImage extends AbstractImage
    {
    	public function __construct($cheminFichier)
    	{
    		parent::__construct($cheminFichier);
    		$this->contenuFichier = file_get_contents(
                                              $this->cheminFichier);
    		$this->tailleFichier = filesize(
                                              $this->cheminFichier);
    	}
    	
    	public function afficherContenu(): ?string
    	{
    		return $this->contenuFichier;
    	}
    }
    
    class ProxyImage extends AbstractImage {
    	
    	private $vraieImage;
    	
    	public function __construct($cheminFichier)
    	{
    		parent::__construct($cheminFichier);
    		$this->tailleFichier = filesize($this->cheminFichier);
    	}
    	
    	public function afficherContenu(): ?string
    	{
    		if (!$this->vraieImage) {
    			$this->vraieImage = new StandardImage(
                                                  $this->cheminFichier);
    		}
    		
    		return $this->vraieImage->afficherContenu();
    	}
    }
    
    final class GestionnaireImage
    {
    	public function traiterImage (ImageInterface $image): void
    	{
    		echo $image->donnerTaille() . ' octets';
    		echo 'Contenu présent ?'.($image->aContenu());
    		echo $image->afficherContenu();
    	}
    }
    
    $gestionnaireImage = new GestionnaireImage();
    
    $image = new StandardImage('elephant.jpg');
    echo $gestionnaireImage->traiterImage($image);
    
    $proxy = new ProxyImage('elephant.jpg');
    echo $gestionnaireImage->traiterImage($proxy);
    

    Le client (GestionnaireImage) travaille d’ordinaire avec des objets de la classe StandardImage, qui, dès qu’elle est instanciée, stocke le flux complet du fichier ciblé dans une variable d’instance. Ceci peut s’avérer extrêmement coûteux si l’image est de grande taille ou si un grand nombre d’images sont requises en même temps par différents utilisateurs de notre classe, voire les deux !

    Nous intercalons donc un objet Proxy entre le client de notre code et le sujet ciblé : ProxyImage possède le même super-type que StandardImage, il est donc tout à fait capable d’agir en qualité de mandataire ! Son rôle sera de différer la construction du sujet ciblé jusqu’au moment où son utilisation sera requise; il doit pour cela posséder une référence à ce sujet, voilà pourquoi vous voyez la donnée membre privée $vraieImage dans ProxyImage ! C’est le mandataire qui instancie le sujet, au départ il possède sur le fichier image une référence indirecte (le nom de fichier) puis finit par obtenir une référence directe (l’objet StandardImage, avec le flux complet).

    Dans le cas de StandardImage, le contenu du fichier cible est intégralement stocké dans la propriété privée dédiée à cet effet, mais pas dans le cas de ProxyImage qui n’instanciera la classe mandatée que lorsque la méthode afficherContenu sera invoquée. La valeur retournée par $image->aContenu() vaudra TRUE dans le cas de StandardImage et FALSE dans le cas du Proxy; c’est bien le signe que le Proxy fait l’économie de la lecture du flux du fichier image sur lequel nous travaillons. Cependant, lorsque le client demande l’affichage de l’image, le Proxy ne peut faire autrement que d’instancier StandardImage pour invoquer dessus la méthode qui va retourner le flux utile à cet affichage.

    Au final, le mandataire est plus efficace que le sujet réel qu’il masque puisque lors de l’appel à donnerTaille dans traiterImage, il n’a pas récupéré l’intégralité du flux binaire du fichier cible. C’est évidemment ce qu’il fait lorsqu’il doit afficher celui-ci, ne pouvant faire autrement.

    Pour utiliser cet exemple, il vous faudra bien entendu une image nommée elephant.jpg que je vous fournis un peu plus bas !

    Quand on doit différer les opérations qui s’avèrent coûteuses lors de la création d’un objet au moment où elle seront effectivement requises alors le design pattern Proxy peut s’avérer d’une aide précieuse !

    La belle image qui a servi à mes tests !

    La belle image qui a servi à mes tests !

    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) :

    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 !

    Les 10 (mauvaises) habitudes du développeur

    Récemment il m’est venu à l’idée, dans un but ludique évidemment, de compiler quelques-uns des défauts les plus couramment rencontrés chez les développeurs. Ce n’est pas une liste exhaustive ni même une charge en règle contre les miens, mais plutôt une espèce d’introspection, d’auto-critique. En effet, quel développeur aurait la prétention de dire qu’il n’a jamais été la victime, consentante ou pas, d’ au moins un des points listés ci-dessous ?

    dirty-computer-keyboard-used-with-gloves

    1 – Le test…en prod !

    Les fonctionnalités sont testées à la va-vite dans un coin par le développeur avant d’être parachutées sans pitié en production. Après tout, on ne teste jamais mieux qu’en condition réelle, n’est-ce pas ? Le (mauvais) développeur n’a jamais entendu parler des tests unitaires, fonctionnels ou d’intégration. Pour lui, le test est un processus visant à démontrer que tout marche à merveille, qu’il a fait du bon travail ! Non, c’est justement tout le contraire : le test est un processus AGRESSIF ! Alors évidemment, des bugs vont survenir en production et le développeur ira les corriger directement sur le serveur, à grands coups de Ctrl+S ou bien, à peine moins sale, en passant autant de fois que nécessaire des branches de hotfixes !

    2 – L’appétit vorace pour la salade de technologies

    Très fréquente chez les jeunes développeurs et principalement dans le milieu du développement web. On utilise des outils à la mode, sans trop chercher à connaître leur niveau de maturité, leur fréquence de mise à jour ou leur résistance à de fortes charges, une fois déployés. Tout le monde en parle et ces technos font le buzz sur des forums très fréquentés, c’est forcément qu’il y a une raison : il nous FAUT les avoir !!! Le code finit par ressembler à une grande marmite dans laquelle on aurait jeté tous les ingrédients trouvé sur les étagères de la cuisine et à terme le code va se retrouver emprisonné dans un écosystème de développement trop hétérogène et qui n’est pas sans rappeler la jungle…C’est une dérive du principe DRY (Don’t Repeat Yourself); comme tout a (presque) déjà été fait, on se contente d’agréger des outils tierce-partie sans forcément en mesurer toutes les conséquences. Ne vous rendez pas inutilement dépendants des autres : si ça existe et que c’est éprouvé, prenez ! Sinon, prenez…le temps de le faire !

    3 – Le code zombie (qui ne meurt jamais vraiment)

    Même lorsque certains développeurs travaillent avec des outils de gestion des versions (SVN, Git), ils conservent la mauvaise habitude de commenter des pavés de code entiers, comme s’ils voulaient garder tout ça « au cas où »…Le vieux code n’est d’aucune utilité, et encore moins si vous utilisez du version control ! Vivez et laissez mourir !

    4 – Le freinage technique

    Le développeur qui est réfractaire au changement constitue un frein technique. Cette fossilisation des pratiques dans des temps reculés (le cambrien ?) est pénalisante pour le reste de l’équipe. Il le sait, mais l’évolution n’est pas la première de ses préoccupations : il a souvent connu l’époque des langages procéduraux et ne voit pas bien l’intérêt de toutes ces choses « nouvelles » qui sont selon lui des lubies de programmeurs inexpérimentés (« Les objets ne sont que des containers passifs de données », « Les classes ne servent à rien », « Les contraintes d’intégrité référentielles contraignent trop le schéma des données »…). Son adage favori ?

    Ça marche, alors on ne touche pas !

    5 – Le nommage inconsistant des variables

    Le (mauvais) développeur ne voit pas trop ce qu’on gagne à donner des identifiants « parlants » à des variables. S’il pouvait toutes les appeler « variable », il le ferait volontiers ! S’il a besoin de matérialiser une assurance, il appellera sa variable « a » : a comme assurance, enfin ! Et puis si demain les cas d’utilisation mettent en évidence la nécessité d’avoir 3 assurances et bien qu’à cela ne tienne, il créera a2 et a3 ! J’ai vu de tout personnellement; des objets nommés o, des booléens nommés b et même des variables portant le nom des personnes qui les avaient créées…les identifiants de variable doivent être courts et évocateurs !

    6 – L’absence d’optimisation

    Quand on écrit une requête SQL, pourquoi se préoccuper du temps qu’elle prend à s’exécuter ? Elle est syntaxiquement correcte et fait (à peu près) le job et puis ça sert à quoi les ORM ? On leur fait aveuglément confiance et tout se passera bien, ils sont faits pour ça ! De même pour le cache web, c’est géré par une couche dédiée à ça dans mon framework favori alors pourquoi devrai-je en plus m’intéresser à tout cela, j’ai déjà bien assez de travail comme ça ! Attention à ce genre de négligences quand on travaille sur des environnements à fort taux de charge ! Ne vous contentez pas de tourner la clé du véhicule et de l’utilisez, ouvrez le capot pour tirer le maximum de bénéfices de vos outils et des bonnes pratiques de développement !

    7 – Le refus catégorique de la documentation

    Le (mauvais) développeur pense qu’il est bon et c’est bien là le drame ! Pour lui, il suffit de lire son (excellent) code pour que tout apparaisse soudainement évident aux yeux de ses camarades codeurs qui vont avoir à reprendre son travail. La documentation c’est compliqué, il faut mettre des mots sur ce que l’on a fait, détailler, éventuellement justifier des choix…Ne négligez pas cette étape, un développeur qui ne documente pas ce qu’il fait, c’est du savoir qui va potentiellement s’évaporer !

    8 – Le copier/coller comme méthode de développement

    La programmation par Ctrl+C/Ctrl+V est un mal qui sévit depuis toujours…A quoi bon réfléchir, il suffit de dupliquer les choses autant de fois qu’il sera nécessaire. Et la maintenance ? Et les 10 classes à passer en revue à la moindre modification ? L’abus de Ctrl+C/Ctrl+V fait perdre 10 fois plus de temps que ce qu’il en fait gagner, mais on ne s’en rend compte souvent que des mois ou des années après; cette façon de faire est une véritable bombe à retardement, génératrice de dette technique !

    9 – L’obsession de la centralisation

    Le (mauvais) développeur croit comprendre que, pour limiter les modifications à tout va quand le temps du changement sera venu, il suffit simplement de tout mettre au même endroit ! Ses classes sont des monolithes de 3000 lignes qui font tout à la fois.

    10 – L’euphorie de la découverte

    Elle se produit lorsque le développeur trouve de nouveaux outils; quand il découvre les classes abstraites, il veut en mettre de partout, idem quand il découvre l’héritage, les fonctions statiques ou les designs patterns. Lorsque la solution est trouvée avant le problème, rien de bon ne peut en résulter. Souvenez-vous de cet adage populaire :

    Quand tout ce qu’on a comme outil c’est un marteau, on a tendance à voir tous les problèmes comme des clous

    photo de clavier sale

    On a les développements qu’on mérite !

    Pimcore : une étude d’opportunité

    PIM, vous dîtes ?

    Le PIM (Product Information Management, gestion de l’information produit en français) a pour objectif de fiabiliser, de détailler et de centraliser les informations sur les produits d’une entreprise. Dans l’environnement de commerce multi-canal actuel, il est nécessaire de contrôler la cohérence et la pertinence de ces informations avant d’alimenter les multiples canaux de diffusion de celles-ci (sur différents médias).

    Le projet Pimcore

    Pimcore se veut un moyen d’agréger, de consolider et d’enrichir ces informations en provenance de diverses sources de données (ERP, système legacy, SGBDR).

    Pimcore est un projet développé par l’entreprise autrichienne Elements1, il est disponible et forkable sur la plate-forme Github :

    https://github.com/pimcore/pimcore.git

    Un dépôt contenant une démo prête à l’emploi est également accessible sur Github :

    https://github.com/ElementsDev4/PimcoreDemo.git

    Pré-requis techniques

    Pimcore a pour pré-requis :

    • l’installation d’Apache (Nginx n’a pas été testé mais ceci ne devrait pas poser de problèmes, il s’agit à priori de traduire les règles de réécriture Apache en règles Nginx1)
    • La présence de MySQL comme SGBDR et la possibilité de garantir à Pimcore que les tables qu’il utilise sont bien en inter-classement UTF-8

    La version qui a été utilisée lors de mes tests est 1.4.52 (Build 1953). Elle embarque une version 3 d’ExtJS et jQuery 1.7.1.ExtJS 4 à ce stade des développements n’est pas pris en charge par Pimcore.

    PHP

    Les paramètres de PHP requis sont les suivants :

    • version >= 5.3
    • memory_limit doit être au moins égale à 256M
    • magic_quotes_gpc doit être à off
    • safe_mode doit être à off (toutefois, cette fonctionnalité est devenue obsolète depuis PHP 5.3.0 et a été supprimée dans PHP 5.4.0)
    • mcrypt
    • pdo_mysql
    • iconv
    • dom
    • simplexml
    • gd
    • multibyte support (mbstring)
    • zlib / zip / bz2
    • openssl

    Les paramètres optionnels sont :

    • l’extension PECL APC
    • l’extension PECL memcache
    • l’activation de pcntl
    • soap (pour utiliser l’API des webservices)
    • curl (pour utiliser l’API Google)

    Il faut aussi penser à modifier les paramètres relatifs à la taille maximale des uploads.

    « Fine-tuning » au niveau du serveur HTTP

    La documentation officielle recommande d’augmenter le nombre maximum de fichiers ouvrables :

    « Étant donné que Pimcore repose sur Zend Framework et d’autres bibliothèques assez lourdes, Apache doit charger un grand nombre de fichiers à chaque requête. Sur Debian, la limite est de 1024 fichiers ouverts simultanément, ce qui est suffisant dans la majeure partie des cas, mais pas pour les sites web à fort trafic »

    Principe général de fonctionnement

    schema_general

    Le principe peut se résumer ainsi: « On extrait du contenu depuis une source de données, on l’agrège, on l’enrichit, on le diffuse ».

    Présentation technique du produit

    Pimcore fonctionne sur une base logicielle de Zend Framework, qu’il embarque dans l’archive téléchargeable depuis le site officiel1.Nul besoin donc d’installer Zend.

    Compatibilité avec les navigateurs

    Pimcore prend en charge un certain nombre de navigateurs de nouvelle génération :

    • Firefox >= 3
    • IE >= 8
    • Safari >= 5
    • Chrome >= 3
    • Plug-ins browser : Adobe Flash Player >= 10.1

    Le cache

    En raison de l’absence de prise en compte de la mise en cache par tags de Memcache, Pimcore a implémenté un cache « maison » basé sur Memcache: Pimcore_Cache_Backend_Memcached.

    Cache backend

    Sur le backend (interface d’administration), différents stratégies de caching sont disponibles:

    • Pimcore_Cache_Backend_Memcached
    • Cache MongoDB
    • Cache fichier (à éviter!)

    Cache frontend

    Côté front, seul Pimcore_Cache_Backend_Memcached est disponible.

    Ouput-cache

    Cible uniquement les requêtes HTTP de type GET. Pimcore rajoute ses entêtes de type X-Pimcore-Cache-*. Si une durée de vie a été spécifiée, les entêtes Cache-Control et Expires sont également présents (intéressant pour une exploitation avec un accélérateur comme Varnish…).

    Pimcore déconseille vivement l’utilisation de la classe « Zend_Cache_Backend_Memcache » fournie par le framework Zend.

    Memcache (ou plus exactement son implémentation « maison » par Pimcore) est indispensable au bon fonctionnement du produit sous peine de voir les temps de réponse augmenter de manière drastique !

    Aspects techniques

    Le coeur de Pimcore

    Pimcore se décompose en trois modules fondamentaux:

    • Les documents : on créé des pages (ou des templates) qu’on remplit avec du contenu
    • Les « assets »: les ressources statiques qu’on insère dans les documents (images, vidéos, PDF…)
    • Les objets : utilisables en mode édition ou bien (et c’est ça qui nous intéresserait davantage) en mode batch avec un script CLI.

    Le backend/frontend

    Le backend est l’interface d’administration Pimcore : c’est là qu’on prépare les templates, qu’on agrège le contenu, qu’on gère les traductions…le frontend est la partie « visible » par l’utilisateur final sur le terminal de son choix (tablette, smartphone, web).

    Développer avec Pimcore

    Se servir de Pimcore suppose une bonne maîtrise de MVC, de PHP et de Zend Framework.

    Développer dans le backend Pimcore c’est finalement savoir se servir du triplet MVC : savoir programmer une action au sein d’un contrôleur, savoir utiliser des vues si besoin, utiliser des patterns de routage, créer des objets « modèle ».

    L’internationalisation (i18n)

    Tous les documents Pimcore sont traduisibles. Évidemment, comme Pimcore embarque Zend, c’est Zend_Translate qui gère cet aspect en sous-main.
    Le backend est traduit en français, le frontend a un module qui facilite la traduction sur les pages proposées à l’utilisateur final.

    Les objets Pimcore

    On crée des objets (Data Objects, selon la terminologie Pimcore) dont on fait correspondre les propriétés avec des éléments graphiques. Ces éléments graphiques ont des types prédéfinis par Pimcore.

    Exemple : Un objet Pneu a comme propriétés « description », « image », « prix », « date_creation », on fera correspondre ces propriétés avec des éléments visuels Pimcore, respectivement « Object_Class_Data_Textarea », « Object_Class_Data_Image », « Object_Class_Data_Numeric » et « Object_Class_Data_Date ».

    Cette étape de mapping est incontournable.
    C’est également à ce niveau là que vont être détaillées les relations entre objets.
    Les objets peuvent être importés comme exportés :

    • l’importation peut se faire sous la forme d’un batch écrit en PHP CLI
    • l’exportation se fait en CSV

    Une attention particulière doit être portée sur la gestion de la capacité mémoire durant les imports, si ceux-ci portent sur un nombre conséquent d’objets. La gestion des versions, si elle n’est pas essentielle, doit être désactivée durant les importations (Version::disable()).

    J’ai trouvé cette partie de la documentation assez sibylline. C’est un domaine à creuser, notamment en parcourant le forum pour y lire les retours d’expérience d’autres utilisateurs.

    Les additifs (plug-ins)

    Il est possible d’en programmer, tout comme il est possible d’en utiliser certains fournis par défaut :

    PhpSearch est un plug-in qui sert de moteur de recherche full-text et qui fournit suggestion et auto-complétion. Il en existe quelques autres, hélas peu documentés :

    http://www.pimcore.org/wiki/display/PLUGINS/Plugin+Documentations

    Toutefois, le développement de plug-ins est plutôt bien détaillé sur ce lien :
    http://www.pimcore.org/wiki/display/PIMCORE/Plugin+Anatomy+and+Design

    Les test unitaires

    Pimcore bootstrappe PHPUnit dans sa suite de test « maison » PimUnit. Un tutoriel vidéo donné en lien en fin de document explique comment installer cet outil dans l’AGL PHP Storm.

    Cette fonctionnalité est quelque chose de nécessaire, dont la présence joue clairement en la faveur du choix final du produit.

    Conclusion

    Forces du produit

    • Pimcore a été primé en 2010 dans la catégorie produits open source innovants par l’éditeur Packt. Ces « Awards » ont récompensé de grands noms comme Joomla, Drupal, WordPress, Jquery, Sencha, Prestashop…
    • Pimcore a déjà des références solides avec des catalogues de grande dimension (plusieurs dizaines de milliers d’objets): Eurotours, Intersport, Expert…
    • L’interface est développée sur une base ExtJS, qui procure une user experience plutôt plaisante. L’utilisation du drag and drop rend le logiciel assez facile à utiliser et lui confère l’apparence d’un logiciel desktop.
    • Le système de plug-ins rend Pimcore extensible.
    • Un mécanisme de mise à jour du produit
    • Courbe d’apprentissage peu abrupte pour des développeurs déjà habitués au MVC
    • La présence de PHPUnit pour l’écriture de tests unitaires (qu’il faut s’astreindre à effectuer !)
    • Compatibilité revendiquée avec des solutions ETL open-source écrites en Java telles que Kettle ou Talend Studio2
    • Une API assez complète donne accès au cœur de Pimcore, sa documentation fait l’objet d’une mise à jour quotidienne (générée sur le dernier nightly build)

    Inconvénients et limitations

    • Une communauté limitée pour l’instant (quelques centaines d’utilisateurs enregistrés sur le forum)
    • Une documentation peu exhaustive, certaines fois inexistante et truffée de fautes
    • Il faut être programmeur pour se servir de Pimcore; une personne sans connaissance basiques de PHP, de l’architecture MVC ne saura pas utiliser Pimcore
    • Interface agréable à l’utilisation certes mais pas forcément très intuitive (il faut parfois fouiller pour trouver)
    • Nécessite une configuration serveur assez particulière, notamment demandeuse en terme de capacité mémoire, les bibliothèques utilisées par Pimcore étant « lourdes »
    • Un import disponible seulement en CSV, il faut écrire un script d’importation à partir de nos sources de données
    • Pimcore a été validé sur des machines fonctionnant avec un serveur HTTP Apache, aucune documentation officielle n’existe sur l’intégration avec Nginx

    Réflexions

    Zend Framework va bientôt évoluer dans sa version 2 (actuellement en bêta1). Comment Pimcore va-t-il intégrer cette évolution ? Cette évolution elle-même sera-t-elle aussi importante que pour Symfony 1.4 → 2.0 ?

    Il faut voir à moyen terme quelle va être la place accordée à MongoDB dans Pimcore. Pour l’heure, il existe juste une classe de gestion de cache basée sur MongoDB mais rien qui concerne le modèle de données à proprement parler.

    Sébastien Ferrandez, Juillet 2012.

    Liens :

    Pimcore

    Zend Framework

    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.