Mot-clef « RBS Change »

Australis, on n'arrête pas la chromocopieuse en marche...

Hier il y a trois jours (c'est ça quand on traine à finir un article), la nouvelle version de l'interface de Firefox, nommée Australis, a été versée dans la nightly (c'est à dire la pré-version v+3, la beta étant la v+1 et aurora est la v+2). Il nous reste donc encore quelques mois avant de l'avoir sur une version en cours.

Alors Australis c'est quoi ? Pour les pressés, je résume en version courte : disons que c'est environ Chrome mais avec un moteur Gecko. Mais si vous avez le temps, vous pouvez lire la suite.

Donc en gros qu'est-ce qui change ? Le point le plus visible et qui fait environ le quart du contenu des quelques articles que j'ai lu sur le sujet c'est les nouveaux onglets aux coins arrondis top cools de la mort qui tue. Si si, la preuve avec la merveilleuse capture qui appuie en général le propos :

Australis - Et le coin arrondi fut.
Australis - Et le coin arrondi fut.

Passé l'instant d'extase où on se dit qu'enfin Firefox a des onglets plus encombrants qui ressemblent à ceux de Chrome, on se rappelle que c'est purement cosmétique et que ça ne change fondamentalement pas grand chose. Du coup on passe à la suite pour voir ce qui change vraiment (non je n'aurais pas fait un article juste pour des coins arrondis, je m'attache souvent à des détails à la con mais faut pas pousser non plus :euh:).

Ce qui change c'est qu'on a quelques autres trucs directement importés de Chrome :

  • encore moins de boutons visibles par défaut. Comme dans Chrome, quoi.
  • un bouton pour le menu, comme dans Chrome, coincé d'office à droite (on risquerait de le perdre si on pouvait le déplacer, donc ne prenons pas de risque ! Si Google l'a mis là c'est qu'il doit y avoir une bonne raison).
  • la disparition du titre de la page courante en titre de fenêtre (pas que par défaut, pas moyen de le remettre). Moi j'aime bien pourtant avoir le titre complet visible et plus de 3 pixels pour pouvoir attraper ma fenêtre en mode plein écran et la changer d'écran mais bon, Chrome ne fait plus ça depuis longtemps, faut vivre avec son temps.
  • sûrement d'autres trucs que j'ai ratés et qui manquaient certainement affreusement aux gens qui ont l'habitude de Chrome et passeraient bien sur un logiciel libre mais sans rien changer à leurs habitudes (pour rappel, il me semble que Chromium n'est pas trop loin de remplir ces critères).

Mais rassurez-vous, comme le dit l'article de présentation de Mozilla :

Firefox can’t simplify by just removing things and still be Firefox — the same less isn’t more for everybody, and Firefox has been successful by being the browser that does things that people, including power-users, want.

J'aurais pas dit mieux (même si moi je l'aurais dit en français). Par contre je sais pas si c'est une tentative d'auto-persuasion, de l'aveuglement ou un abus de substances illicites qui font voir de éléphants rouges mais en fait ils ont environ fait l'inverse puisqu'un certain nombre de libertés d'organisation ont sauté. En effet, après quelques tests, j'ai relevé ça :

  • les boutons précédent, suivant et rafraichissement qui étaient jusqu'à présent déplaçable n'importe où sont maintenant soudés à la barre d'URL, de part et d'autre, histoire de faire travailler un peu la souris. Donc plus moyen de les mettre ensemble où on veut.
  • l'option de placer les onglet en dessous ou au dessus de la plupart des barres d'outils n'est plus prise en compte. Il parait que c'est mieux, plus logique et tout et tout... Personnellement j'ai que la barre d'URL et les boutons qu'elle a phagocyté (voire point suivant) qui soient en rapport avec l'onglet courant, donc avoir la barre au dessus, je trouve pas si logique que ça... Mais bon, Chrome fait comme ça, donc...
  • la barre d'URL qui pouvait avant se placer sur n'importe quelle barre est maintenant coincée sous les onglets. Parce que oui on peut avoir une barre de menu au dessus des onglets avec le moteur de recherche et des éléments perso mais pas la barre d'URL, ça c'est mal... sauf que moi j'ai l'habitude de la placer avec des éléments de taille adaptée pour pas perdre de place. Et si j'ai envie de la mettre ailleurs, pourquoi m'en empêcher ?
  • symétriquement, si on fait apparaitre la barre de menu (vous savez celle avec du texte et des entrées assez stable d'une application à l'autre mais qui a tendance à disparaitre de plus en plus), celle-ci est coincée au dessus des onglets. C'est con, moi j'avais l'habitude d'avoir la barre d'URL et le menu sur la même ligne... Pourquoi diable m'en empêcher ?
  • la barre de modules disparait... Donc on ne mettra plus aucune info en bas. C'est mal le bas sans doute.

Donc c'est clair : j'ai pas moyen de placer la moitié des éléments comme je les place actuellement mais cette nouvelle version est super personnalisable. Si si, c'est écrit c'est forcément vrai ! (comme quand Apple dit que les empreintes digitales ne quitteront pas le téléphone)

Les empreintes digitales seront exclusivement stockées sur le téléphone ! #OnYCroit
Les empreintes digitales seront exclusivement stockées sur le téléphone ! #OnYCroit

Après je dis pas que tout est mauvais, hein. Le bouton de menu ouvre un menu qui est vraiment personnalisable, lui et ça c'est très bien. Le hic c'est que pouvoir personnaliser un menu qui ne s'ouvre qu'en cliquant sur un bouton c'est un piètre palliatif à la personnalisation de l'interface visible. J'ai un écran de taille largement suffisante pour me foutre totalement de perdre 25 pixels avec une barre d'outils. Si si je vous assure.

Alors je veux bien croire qu'Australis est sûrement plus personnalisable que Chrome (ce qui ne me semble pas trop dur pour le peu que je l'ai essayé). Mais moi mon critère de comparaison c'est pas Chrome, c'est Firefox. Parce que oui, c'est Firefox que j'utilise depuis de nombreuses années, Chrome il est installé à côté juste pour pouvoir détecter les bugs liés à Webkit dans les sites que je développe (je reconnais donc volontiers que je risque de le voir de plus en plus souvent étant donné à quel point la situation empire progressivement de ce côté là mais c'est un autre débat). Et comparé à mon Firefox actuel, c'est nettement moins personnalisable.

Et ne me dites pas que c'est pour mon bien si je peux plus déplacer mes éléments comme je veux. Si j'avais voulu qu'on décide pour moi, j'aurais pris un navigateur Apple.

Donc oui cette nouvelle interface ravira sans doute les transfuges de Chrome, ainsi que les gens qui ont peur quand ils voient plus de 3 boutons dans une application (même si je suis sûr que de ce côté là on peut encore faire beaucoup mieux). Mais ce serait sympa de pas oublier les utilisateurs avancés. Mais vraiment pas oublier, hein, genre un truc qui se traduit par des faits pas juste un texte qui dit le contraire des faits (pour ça on a déjà des politicien(ne)s).

D'autant que là on ne parle pas de fonctionnalités supprimées qui simplifient le code pour le rendre plus maintenable et plus léger, hein, je parle de bridages forcément volontaires qui jusqu'à preuve du contraire en rajoutent donc !

Bon, heureusement, restent deux points positifs. D'une part, c'est encore en nightly, il peut donc largement y avoir des ajustements d'ici à la version finale (notamment pour réintroduire de la flexibilité là où elle est inutilement absente). Et d'autre part Firefox a un système d'extensions qui fait qu'ont trouvera rapidement de palliatifs à tout ça (notamment cette extension semble prometteuse). Mais c'est lourd à force de devoir installer des dizaines d'extensions et farfouiller dans about:config non pas pour améliorer son navigateur mais juste lui éviter de régresser.

Donc vous l'aurez compris, j'adoooore l'évolution de Firefox :) Il a de la chance d'avoir peu de concurrents libres et cross-plateforme de qualité.

D'autant que comme j'ai pu le lire sur Twitter :

Je déteste aussi Australis parce que j'y vois une étape dans le remplacement du puissant Firefox Desktop par son très limité cousin mobile

— Clochix (@clohix) le 19 novembre 2013 à 20h00

Et ça c'est vraiment flippant quand on voit à quel point Firefox est limité sur Android...

P.S. : J'ai la flemme de tout relire pour savoir si j'en ai parlé ou pas mais dans le doute, je dirais que globalement, ça ressemble un peu à Chrome. Mais juste un peu et c'est sûrement fortuit !

P.S. 2 : Bon, le bon point c'est que cette fois ils ont rien cassé dans le backoffice de Change \o/ (oui c'était pour tester ça initialement quand j'ai mis à jour ma nightly...)


Quelques petites BD #4

Voici ma quatrième fournée de petites BD en quatre cases (oui je suis trèèèès loin du rythme d'un Commit Strip :euh:).

BD #11 - 06/03/2013 BD #12 - 29/09/2013 BD #13 - 22/10/2013
Images sous licence CC-BY

La seconde nécessite quelques précisions :

  • RBS Change, j’en ai déjà parlé ici plusieurs fois, c’est le CMS / solutions e-commerce auquel je contribue au boulot
  • Magento est une solution e-commerce concurrente (l’une des plus connues)

Côté technique, j'ai finalement progressivement abandonné le brouillon papier pour tout faire directement à la tablette graphique, c'est plus simple une fois qu'on a un peu l'habitude ^^ Les plus observateurs auront constaté que je recycle allègrement les éléments graphiques des précédents numéros... le recyclage c'est bien, c'est écologique et ça fait gagner du temps :smile:


De l'intérêt d'utiliser des standards

Lorsqu'il y a peu, Opera a annoncé sa migration sous Webkit, certains se sont réjouis à l'idée de voir réduire le nombre de moteurs de rendus et donc la diversité des navigateurs à supporter lorsqu'on développe un site.

D'autres ont rétorqué qu'une réduction de la diversité, si elle pouvait potentiellement faire gagner du temps à court terme, ne pouvait qu’être néfaste à long terme pour le web en général. En effet, le développement des standards et leur respect dans les différents navigateurs ne se fait que parce que plusieurs moteurs ont une part de marché significative. Pour preuve : à l'époque où Internet Explorer faisait 95% de part de marché, non seulement l'innovation se faisait uniquement en mode propriétaire à la seule initiative de Microsoft mais pire encore, elle a fini par s'assécher totalement après la sortie d'IE6. Seule l'arrivée de Firefox a fini par relancer la machine.

Actuellement, si Webkit est encore loin d'atteindre la part de marché passée d'IE sur les ordinateurs de bureaux, il est largement majoritaire sur les mobiles et tablettes, ce qui conduit de plus en plus de développeurs à ne développer leurs sites et applications web que dans l'optique de webkit, ignorant totalement les autres.

Faire ce choix de la facilité est à mon sens une grave erreur car si webkit est largement majoritaire actuellement, qu'en sera-t-il dans un ou deux ans ? Nul doute que l'écart se sera resserré si Firefox OS prend des parts de marché significative. Je doute assez peu que ce soit le cas, la seule inconnue restant la taille de cette part.

L'importance de respecter les standards est essentielle pour fonctionner sur un large panel de navigateur. Et fonctionner sur un large panel de navigateurs en respectant les standards (plutôt qu'en abusant de hacks en tous genre ou en développant des version parallèles) est non seulement plus maintenable mais aussi plus pérenne.

En effet, quand on se base sur les spécificités d'un navigateur, on est à la merci de son évolution. Car si les standards implémentés dans les différents navigateurs sont destinés à durer (un site qui fonctionnait bien sur les standards d'il y a dix ans fonctionne toujours actuellement), les éléments spécifiques, eux, peuvent disparaitre ou changer du jour au lendemain.

Nous en avons fait la douloureuse expérience avec RBS Change ces derniers mois. En effet, le backoffice est basé sur XUL, une technologie propriétaire de Mozilla, donc limitée au seul moteur Gecko (donc Firefox essentiellement). Depuis qu'elle a renoncé il y a quelques années à en faire un standard, Mozilla tend à réduire progressivement la portée de XUL. Initialement utilisables pour des applications web standard comme alternative plus riche à (X)HTML, XUL n'est actuellement plus utilisé que pour l'interface de Firefox. Pour continuer à l'utiliser dans le backoffice de Change, il nous faut passer par une extension.

Or la version 17 de Firefox a introduit un changement qui entrainait un plantage de Firefox dès l'ouverture du backoffice. Ce bug, critique pour les utilisateurs de Change était loin de l'être pour Mozilla puisque si un patch a été proposé en quelques jours par un contributeur externe, la validation du patch et son intégration dans Firefox a pris plusieurs mois. Et ce n'est que dans la future version 20, soit trois version plus tard qu'il sera enfin corrigé (alors qu'il avait été remonté le jour de la sortie de la 17).

Le choix de reposer sur XUL, s'il semblait bon en 2005 lorsqu'il a été fait (et l'était probablement étant donné ce qu'il permettait à une époque où les frameworks JavaScript étaient balbutiants et où les API standard étaient bien plus limitées) s'avère aujourd'hui avoir des conséquences coûteuses : redévelopper l'intégralité de l'interface ça a un coût assez élevé. Avec une technologie standard on n'aurait pas eu ce problème. Certains pans auraient dû être repensés de toutes façons mais cela aurait pu être bien plus progressif.

Je viens d'évoquer une techno 100% propriétaire comparable à Flash ou Silverlight par exemple dans le sens où, propulsée par un seul éditeur elle peut du jour au lendemain cesser d’être maintenu ou bannie de certaines plateformes. On est donc assez loin de ce qu'on risque de voir arriver avec le ciblage en "Webkit only", du moins dans un premier temps.

Mais les problèmes de ce choix de XUL a d'autres conséquences moins évidentes. En effet, choisir XUL c'est faire du développement dédié à Firefox uniquement. C'est donc se permettre d'utiliser des spécificité de Firefox à tous les niveaux, voire d'en exploiter sans s'en rendre compte ce qui s'avère être des bugs.

Nous en avons fait l'expérience une fois le bug évoqué plus haut puisque même si le navigateur ne plantait plus, le backoffice n'en était pas pour autant redevenu fonctionnel ! En effet à partir de Firefox 17, E4X (déprécié et désactivé dans la version 10) ne cause plus des erreurs seulement lorsqu'il est utilisé mais déjà des erreurs de parsing lorsqu'il est simplement présent dans un fichier. Or si on avait fait en sorte de ne plus passer dedans pour un usage normal, certaines fonctionnalités dépréciées de Change en contenaient encore... nous avions donc le choix entre tuée définitivement ces fonctionnalités ou assurer la compatibilité avec les version actuelles de Firefox. C'est le second choix qui a été fait mais d'une part ça implique qu'il faut mettre à jour Change pour pouvoir tourner sur les dernières versions de Firefox et d'autre part ça a impliquer de tuer des fonctionnalités (certes dépréciées depuis longtemps) dans une version corrective, ce qui n'est pas forcément très réjouissant.

De même, Firefox 20 modifie le comportement de la méthode setAttribute() sur un élément du DOM. Jusqu'à présent en affectant la valeur null, cela affectait au final un chaine vide et maintenant cela affecte la chaine "null". En soi c'est très bien puisque c'est conforme à ce que font tous les autres navigateurs mais ça a des conséquences sur le fonctionnement de l'interface. Si nous avions travaillé en contexte multi-navigateur, nous aurions remarqué cela très tôt. Là encore une fois, ça impose une mise à jour de Change pour rester compatible avec les derniers Firefox.

Et c'est bien cela qui pend aux nez de tous ces développeurs qui font du "Webkit only" : lorsqu'un bug sera corrigé, si leur applications reposent dessus (parfois sans qu'ils s'en doutent), il leur faudra faire des correctifs en urgence pour réparer voir coincera des entreprises sous des vieilles versions comme cela a pu et est encore le cas avec IE6. Et cela arrivera de plus en plus souvent si Webkit continue à progresser en part de marché.

Donc on ne le dira jamais assez : utilisez des standards (et de préférence matures, sans reposer sur des propriétés préfixées) et validez vos développement même mobiles sur plusieurs moteurs différents ! Ne reproduisez pas le désastre d'IE6.

Et en ce qui me concerne, sur ma tablette Android, par principe je ne lance jamais aucun navigateur sous Webkit (sauf tests justement). Jusqu'à présent j'utilisais Firefox et Opera. Maintenant je n'utiliserai plus que Firefox. C'est dommage mais c'est comme ça. Et si un site ne fonctionne que sous Webkit, tant pis pour lui.


Application web de gestion d'achats groupés

Voici un autre module Change que je suis en train de développer. Pour le coup il répond à un besoin très spécifique et correspond plus à un extranet qu'à un site web classique. Mais bon, puisque moi j'en avais besoin, peut-être que d'autres en auront l'usage aussi. Ou pas mais on peut difficilement savoir avant d'en parler :)

De quoi s'agit-il ?

Depuis des années, j'achète souvent sur le net, en particulier des biens culturels et ludiques : livres, mangas, DVD CD, jeux de plateaux, jeux à collectionner, jeux vidéos, etc. Grouper les achats permet en général de réduire les frais de port et en bonus allège un peu le coût écologique total des livraisons. Donc on a pris l'habitude avec mes frère, sœur et parents principalement de grouper nos achats de ce type. Ceci implique de gérer des comptes puisqu'à chaque commande, une seule personne paye effectivement et le autres doivent le rembourser. Le même principe vaut pour les cadeaux groupés ou autres courses occasionnellement faites pour le compte des autres.

S'échanger du liquide à chaque fois c'est chiant, donc pendant longtemps on est passés par du mail pour se tenir au courant de l'état du solde de chacun. Mais le mail c'est quand même assez peu adapté : quand on se plante et qu'on part du mauvais mail pour calculer les nouveaux soldes ou quand on se demande si on a bien pensé à prendre en compte tel ou tel achat c'est vite le bazar. Puis faut faire tous les calculs à la main, c'est lourd et ça introduit un risque d'erreur non négligeable.

Du coup comme on est quand même une famille de développeurs on se disait que faire une appli web accessible de n'importe où pour gérer tout ça ce serait cool et ça nous faciliterait la vie (enfin sauf ma mère qui elle se cantonne en général à rappeler qu'en achetant moins ça serait tout de suite plus simple mais avant de prendre sa retraite elle était prof de maths, pas informaticienne, donc on lui pardonne). Comme toujours, avec les supers solutions ça s'est éternisé quelques temps à l'état de projet hypothétique parce que bon, c'est bien beau de se dire qu'il faudrait le faire mais c'est quand même vachement plus simple que de le faire réellement.

Mais bon, un jour on se dit qu'au final si on veut pas arriver au point où on aura passé plus de temps à se dire qu'il faudrait le faire que le temps nécessaire pour le faire réellement il faut se lancer. Et donc il y a quelques mois j'ai pris mon courage à deux mains et finalisé une première version utilisable (loin d'être complète et ergonomiquement optimale mais utilisable).

Comme j'ai l'habitude de travailler avec Change, c'est donc un module Change. Ça aurait pu être fait avec n'importe quelle autre techno mais c'est celle là que je maitrise.

Bon et donc ça fait quoi concrètement ?

Pour l'instant ce module va vraiment à l'essentiel : il gère des "factures" (que j'aurais pu appeler aussi "transactions") correspondant à un achat dans une boutique avec le montant total payé, les réductions et les frais inclus dans ce total et enfin le détail des achats avec la répartition entre les acheteurs.

Formulaire de création d'une facture
Formulaire de création d'une facture

L'outil valide la cohérence globale de ce qui est saisi, en particulier :
- les répartitions doivent correspondre au total des quantités saisies
- le montant payé doit correspondre au total des lignes après application des réductions et frais
On évite ainsi pas mal d'erreurs de saisie.

Validation de la cohérence de la facture
Validation de la cohérence de la facture

Une fois la facture enregistrée, la répartition des coûts entre les différents acheteurs est calculée (les réductions et frais indiqués étant répartis au prorata des achats) et leurs soldes sont automatiquement mis à jour. Les différents intervenants sont alors avertis par e-mail qu'une facture qui les concerne vient d'être saisie.

Détail d'une facture
Détail d'une facture

On peut également consulter la liste des comptes utilisateurs avec leurs soldes et accéder au détail d'un compte qui donne la liste de ses factures.

Liste des comptes
Détail d'une facture
Détail d'un compte
Détail d'un compte

Et enfin on peut également accéder à l'historique complet des factures enregistrées.

Liste des factures
Liste des factures

C'est tout pour le moment, ce qui suffit à répondre nos besoins minimaux et apporte déjà nettement plus de simplicité que le mail et moins d'erreurs grâce aux différents contrôles de cohérence.

Évolutions envisagées

Même si l'ensemble répond au besoins minimaux, c'est encore loin d'être complet et ergonomique.

Gestion des boutiques

En effet, la création d'une nouvelle boutique implique de passer par le backoffice, ce qui est assez contraignant. De même il n'y a pas encore de listing des boutiques sur le site qui permettrait de suivre l'historique des dépenses dans chacune d'entre elles.

Saisie des factures

La saisie des factures est encore assez figée : tous les comptes ont systématiquement leur colonne dédiée, ce qui fonctionne à note échelle actuelle de 5 personnes mais poserait des problèmes avec une plus grosse volumétrie (qui pourrait être vite atteinte si l'on ajoute des acheteurs plus occasionnels).

De même le nombre de lignes est figé d'autorité à 30, ce qui est à la fois beaucoup trop la plupart du temps et pas assez dans certains cas exceptionnels.

Modification et validation des factures

À l'heure actuelle, les factures sont "validées" automatiquement lors de leur saisie. C'est à dire qu'elles ne sont pas modifiables et que les soldes des intervenants sont mis à jour immédiatement.

Pour une utilisation plus pratique et rigoureuse il est prévu d'ajouter une validation par l'ensemble des intervenant avant de mettre à jour les soldes, ceci permettant de détecter les éventuelles erreurs. De plus tant qu'elle n'est pas validée par tout le monde, la facture restera modifiable, voire annulable.

Autres évolutions envisagées

D'autres évolutions seraient intéressantes à mettre en œuvre :

  • Gestion de versements directs : dans notre cas, certaines personnes passent plus fréquemment des commandes que d'autres. Il en résulte que les soldes ont tendance à se déséquilibrer, déséquilibres qui se règlent en général par un versement (liquide, chèque, virement...). Il serait donc intéressant de pouvoir matérialiser dans une entrée dédiée un versement direct d'une personne à une autre dans un formulaire simplifié.
  • Pour certains achats (au hasard les cadeaux), il peut être pratique de ne rendre visibles certaines commandes (voire certaines lignes seulement) que par les personnes concernées (notamment pas le destinataire du cadeau).

Je ne dis pas que tout sera fait et encore moins sous quels délais mais dans l'idée c'est pour l'instant l'essentiel qu'on ait détecté comme évolutions à faire pour améliorer l'appli.

Accès au code

Comme la plupart de mes modules, celui-ci a vocation à être publié (d'ailleurs en théorie l'Afero GPL l'impose un peu) une fois qu'il sera suffisamment finalisé. Je compte également l'archiver dans un repository publique mais je n'ai pas encore tranché entre passer par un site existant genre GitHub ou SourceForge comme pour mes précédents modules (qui aurait l'avantage d'une mise en place simplifiée) et m'installer un repository local sur mon serveur avec un accès web au fichiers (qui d'un côté me plait mieux parce que je garde la maitrise de mon repository plutôt que de le stocker sur une plateforme tierce centralisée mais implique de passer du temps à le mettre en place).

Quoiqu'il en soit, si jamais quelqu'un est intéressé (sait-on sait jamais) je peux déjà lui fournir la version actuelle sur simple demande ^^


Historique des méthodes de surcharge de code PHP dans RBS Change

Le but de cet article est de revenir sur les différentes méthodes de surcharge du code PHP qu'on a eu (et que pour beaucoup on a encore) dans RBS Change depuis que je travaille dessus (au départ en faisant des sites avec puis par la suite en tant que développeur du produit).

Je n'entrerai pas dans les détails concrets d'utilisation mais ça me semble intéressant de voir un peu l'évolution des concepts, les différentes tentatives qu'il y a eu et les raisons qui ont pu orienter certains choix.

En fin d'article je présente ce qui sera fait dans la version 4.0 dont la sortie n'est prévue que courant 2013. En conséquence, ce que j'évoque pourrait potentiellement encore évoluer d'ici là et ne reflète que ce qui a été mis en œuvre pour l'instant, pas une vérité gravée dans le marbre.

Écrasement des fichiers dans la webapp

J'ai commencé à travailler avec Change sur la version 1.2 en 2006. À cette époque le seul moyen offert pour surcharger du code du produit était d'écraser certains fichiers (PHP ou autres, cela fonctionnait pour tous les fichiers des modules et même du framework : PHP, XML, templates, styles, etc) dans le dossier "webapp". La mécanique était (trop) simple : à chaque chargement de fichier PHP, l'application commençait par le chercher dans le dossier webapp. S'il était présent cette version était chargée, sinon celle du code standard l'était. Le fichier de surcharge était donc contraint de reprendre l'intégralité du code qu'il surchargeait, même au cas où il réécrivait seulement une méthode d'une classe en comptant cinquante.

C'était toujours un peu mieux que de modifier directement le code du produit mais à peine puisque lors d'une mise à jour, il fallait passer sur chaque fichier surchargé pour ré-appliquer les modification.

Dès la version 2, l'année suivante, la possibilité de surcharge de code PHP directement dans le dossier webapp a été supprimée car elle posait beaucoup trop de problèmes. En remplacement, une autre méthode plus cadrée a été mise en place : "l'injection" de service et peu après, de document.

Injection de service et de document

L'injection de service (toujours d'actualité dans la version 3.6) fonctionne sur un principe qui reste très simple mais qui est nettement plus cadré. Dans la configuration projet déclare des injections consistant à dire "le servie A est à remplacer par le service B". Tous les service sont instanciés via une méthode getInstance() (selon le patron de conception singleton) qui inclut une mécanique qui va chercher dans la configuration projet si une injection a été déclarée sur ce service. S'il y en a une, on retourne une instance de la classe remplaçante au lieu de la classe appelée.

Le code travaille donc simplement avec une autre classe que celle qu'il a demandé. Pour que les choses marchent bien, il faut évidemment que la classe remplaçante étende la classe remplacée.

Peu après le même principe a été appliqué aux documents : en déclarant une injection de document on remplace son modèle, sa classe finale et son service là encore en renvoyant des instances de classes étendant celles que le code demande en réalité.

Globalement ça marche assez bien mais avec deux limitations :

  1. Dans le cas où on injecte une classe déjà étendue par ailleurs, les classes étendues ne profiteront pas de l'injection. Par exemple, si une classe C étend A et que j'injecte A par une classe B. Si je demande une instance de A, j'obtiendrai une instance de B mais si je demande une instance de C j'obtiendrai une instance de C étendant A et non B.

    Cette limitation peut rapidement s'avérer gênante quand on développe des modules complexes avec des documents de base étendus par d'autres (par exemple les différents types de produits dans le module Catalogue et boutiques).

  2. Une même classe ne peut être injectée qu'une seule fois. Cela peut devenir problématique si plusieurs modules veulent se greffer sur un module existant et ont besoin de remplacer du code : il faudra alors écrire une classe en spécifique dans le projet qui cumules les différentes modifications ce qui n'est pas très bon en terme de maintenabilité (on se rapproche à nouveau un peu de l'écrasement décrit plus haut même si ça reste plus restreint).

Points d'entrée spécifiques (stratégies, section "mvc" du project.xml, etc.)

Parallèlement à l'injection, d'autres mécaniques plus ciblées ont été mises en place dans certains modules pour permettre de remplacer du code standard par des implémentations spécifiques.

Les modules liés à l'e-commerce se sont ainsi enrichis d'un certain nombre de "stratégies" (du patron de conception strategy). Ces points d'entrée explicites permettent de choisir entre plusieurs implémentations pour un traitement donné. Par exemple on proposait une stratégie sur l'arrondi des prix avec une implémentation standard qui se contentait d'arrondir à deux chiffres après la virgule, là où certains projets avaient d'autres règles.

On trouve encore ce genre de chose en version 3.6, en particulier sur les points le choix n'est pas unique au sein d'un projet donné (par exemple le calcul du montant d'un frais : deux frais peuvent s'appliquer avec des stratégies de calcul différentes).

On en trouve encore quelques autres dont l'implémentation est choisie une fois pour toutes dans la configuration projet mais elles tendent à disparaitre (cette mécanique spécifiquement mise en place à chaque fois étant alors redondante avec les mécaniques génériques d'injection).

D'autres points d'entrée spécifiques ont été mis en place au fur et à mesure, notamment pour les différentes classes entrant en jeu dans le modèle MVC (notamment les différents contrôleurs) qui peuvent être remplacée via une section dédiée dans la configuration projet.

Dans tous les cas ces points d'entrée ont été mis en place spécifiquement dans certaines parties du code. Les mettre en place implique donc de prévoir volontairement que telle ou telle partie doit pouvoir être remplacé au contraire de l'injection qui fonctionne de manière plus globale.

Réécriture de classe et AOP

Dans la version 3.0 de Change, un nouveau mécanisme a été implémenté, tiré de certains concepts de la programmation orientée aspect (AOP).

Cette mécanique propose plusieurs choses reposant sur la réécriture des classes lors d'une phase de compilation.

Premièrement le remplacement d'une classe par une autre. Il s'agit là non pas de simplement retourner une instance d'une autre classe (comme c'était le cas avec l'injection décrite plus haut) mais bien de réécrire la classe pour y intégrer les modifications. Pour ce faire, on renomme la classe d'origine (avec un suffixe quelconque) puis on renomme la remplaçante avec le nom de l'originale. Ainsi le remplacement est totalement transparent pour le code qui l'entoure : il travaille bien physiquement avec une instance de la classe qu'il a demandé, à ceci près que cette classe a été modifiée.

Par exemple on part d'une classe A que l'on veut remplacer par une classe B (qui doit étendre A). On commence par renommer A en A_replaced0. Puis on renomme B en A et on la fait étendre A_replaced0. On obtient alors une nouvelle classe A qui a toutes les méthodes et propriétés de la classe B, y compris celles de la classe A d'origine.

Cette méthode fait sauter la limitation de l'injection décrite plus haut sur les classe étendues puisque du coup une classe C qui étendrait notre classe A d'origine héritera du même coup du code introduit par B. Dans son implémentation proposé dans la version 3.0 (qui reste la même en 3.6), elle ne fait par contre pas sauter l'autre limitation liée aux sources multiples.

Depuis la version 3.0, c'est sur cette mécanique que repose l'injection de documents et plus sur la mécanique décrite auparavant (ainsi une injection du document catalog/product se répercutera sur tous les documents qui l'étendent : catalog/simpleproduct, catalog/productdeclination, etc.).

Au delà de cette simple mécanique de réécriture (qui n'est pas de l'AOP à proprement parler mais juste un préalable), d'autres possibilités ont été mises en place permettant d'ajouter du code avant ou après l'exécution d'une méthode, d'ajouter une méthode à une classe ou d'en remplacer une (il s'agit là effectivement des concept de l'AOP). Là aussi on fait de la réécriture de classes mais avec l'avantage de pouvoir faire venir des modifications de plusieurs sources.

L'inconvénient est que dans les fait c'est un peu compliqué à comprendre et expliquer (le développeur web n'étant pas forcément familier avec ça) et pas forcément très lisible ni intuitif à écrire. Du coup finalement ça a été assez peu utilisé dans la pratique à part pour contourner la limitation sur les sources multiples ou bien pour agir sur des méthodes privées d'une classe (ce que permet dans une certaine mesure l’implémentation actuelle mais relève à mon sens un peu de l'hérésie... même si dans certains cas c'est tentant, réécrire des méthodes privées c'est réécrire du code qui n'est pas censé être accessible par l'extérieur et c'est très mauvais lors des mises à jour).

Injection de blocs

La dernière mécanique en date à avoir été mise en place est l'injection de blocs qui arrive avec la version 3.5.

Lorsqu'on insère un bloc dans une page, on ne référence pas directement un nom de classe mais un "type" (celui qui est indiqué dans le fichier blocks.xml du module) qui en pratique découle du nom de la classe. Mais a grosse différence avec les service c'est que le bloc n'est pas utilisé en tant que tel par du code des modules. Rien n'oblige donc au final à ce que le bloc finalement rendu soit réellement le bloc identifié par le type.

Lorsqu'on utilise un service on va faire appel à ses méthodes pour effectuer des traitements si une méthode manque ou n'a pas la bonne signature la plupart du temps ça finit avec une belle page dont le contenu commence par "Fatal error:". Lorsqu'on utilise un bloc, on va simplement lui demander de se rendre mais son implémentation importe peu en terme de réussite ou d'échec de l’exécution.

L'injection de bloc peut donc être nettement plus libre que l'injection de service ou le remplacement de classe. En effet, elle se contente de changer le mapping entre le "type" du bloc et la classe PHP effectivement utilisée sans obligation que la nouvelle classe étende la classe d'origine (du moment que ça reste un bloc). Si on reste sur un fonctionnement proche, on étendra le bloc d'origine mais si ce n'est pas le cas on peut aussi repartir d'une feuille blanche et le remplacer par une nouveau bloc totalement différent (voire étendant un autre bloc existant qui se rapproche plus de ce qu'on veut faire).

À venir en 4.0 : unification et simplification

Comme on a pu le voir, les méthodes de remplacement de code disponibles sont très nombreuses avec chacune leur limitations. De tout ce que j'ai pu évoquer, trois sortent du lot car elles apportent vraiment un plus en terme de possibilités (hors des limitation liées à l'implémentation). En effet toutes finalement visent à remplacer des classes par des classes qui les étendent une fois pour toutes globalement pour le projet à l'exception de :

  • l'injection de document est un peu à part puisqu'elle implique plusieurs classes (modèle, classe finale du document, service) mais au final reste du remplacement de classe
  • les stratégies dont le choix ne relève pas de la configuration projet mais se fait au cours de l'exécution selon le contexte (exemples : calcul de frais, calcul de réductions, etc)
  • l'injection de blocs qui n'impose pas de reprendre le code du bloc existant mais peut repartir de zéro dans une autre direction si le besoin s'en fait sentir

Toutes les autres (injection de service, remplacement de classe, AOP, stratégies définies dans la configuration projet, remplacement de classes spécifiques) répondent bien au même besoin : introduire du code spécifique dans une classe existante en ciblant au mieux la partie réécrite pour avoir le moins de problème possible lors des mises à jour. En version 4.0 ces différentes mécaniques parallèles seront donc supprimées au profit d'une seule et unique mécanique reprenant le meilleur de chacune.

Elle s'appellera "injection de classe" et pourra d'une manière générale être appliquée à n'importe quelle classe (à part une poignée qui sont chargées trop tôt dans l'exécution pour pouvoir en profiter). Elle reposera sur le concept de réécriture de classe (comme la mécanique de remplacement de classe) et sera déclarée dans la section injection sur le modèle de l'injection de service actuel.

Reste à couvrir un point que permettait l'AOP : injecter une classe avec du code provenant de plusieurs sources. Conceptuellement, rien ne l'empêche. C'est uniquement l'implémentation mise en œuvre dans les versions précédentes qui ne le permettait pas.

En version 4.0, on pourra donc déclarer des "injections chainées" en gros au lieu d'indiquer une seule classe de remplacement, on pourra en indiquer plusieurs séparées par des virgules. La mécanique de remplacement sera appliquée successivement dans l'ordre indiqué.

Par exemple, si une classe A est déclarée injectée par trois classes B, C et D (qui doivent toujours toutes étendre A), on obtiendra la chaine d'extension suivante : A_injected0 (anciennement A), A_injected1 (anciennement B) qui étend A_injected0, A_injected2 (anciennement C) qui étend A_injected1 et enfin A (anciennement D) qui étend A_injected2. De cette manière plusieurs modules peuvent proposer leur injections spécifiques sans exclure les autres.

Ceci évitera au développeur de se retrouver à devoir choisir entre une multitude de mécaniques proches avec chacune ses limitations pour n'utiliser toujours qu'une seule et même mécanique simple à comprendre et à mettre en œuvre.

Conclusion

Voilà, j'ai fini mon petit tour de l'historique des méthodes de surcharge. Je me suis concentré sur ce qui est lié directement aux classes PHP, omettant volontairement tout ce qui a trait aux templates, styles et fichiers de configuration des modules (déjà comme ça ça donne un sacré pavé).

Comme je le disais en introduction, je ne suis volontairement pas entré dans les détails de mises en œuvre, pour cela référez-vous à la documentation de Change ou posez vos questions sur le forum.

Comme je l'ai dit également, la dernière partie concernant la version 4.0 ne reflète que l'état actuel des développement et pourrait éventuellement évoluer encore un peu d'ici la sortie. Mais globalement les grandes lignes devraient rester valables. On notera du coup qu'il n'est pas conseillé d'abuser de l'AOP dans un projet actuel car cela nécessitera beaucoup de boulot de migration vers la version 4.0 au contraire d'un remplacement de classe qui reste le plus proche de ce qu'on aura au final (c'est d'ailleurs pour cela que l'AOP n'a pas été documentée dans le wiki).

Voilà, félicitations à ceux qui liront mon pavé jusqu'au bout... à supposer qu'il y en ait :euh: