Gestion de la modularité sur Magento : faites les bons choix !

magento-optimisez-la-gestion-modulaire-vignette

Magento est une plateforme modulaire, et cela se traduit par le fait que d’un point de vue technique et structurel, des choix d’implémentations ont été faits. Le but de ce billet est de montrer comment et pourquoi vos développements doivent considérer cet état de fait, pour améliorer la maintenabilité de votre site, et conserver une cohérence structurelle avec le framework Magento.

[toc ordered_list= »false »]

1. La modularité sur Magento : qu’est ce que c’est ?

J’avais déjà pu évoquer rapidement cet aspect pour un autre de mes articles, mais la modularité d’une application va se définir de façon empirique comme suit :

D’un point de vue fonctionnel la modularité consiste à rendre l’application capable d’installer d’autres composants qui vont enrichir le fonctionnel de cette dernière ou éventuellement le modifier, de préférence sans impacter son noyau.

D’un point de vue technique, la définition générique nous indique que cela permet de séparer l’application en groupement fonctionnel pour permettre, pour l’essentiel :

  • de factoriser du code
  • que les développeurs puissent travailler sur des parties bien distinctes
  • ….

Mais il existe un dernier point, qui à mon sens est majeur, et sur lequel Magento a axé une partie de son travail, c’est bien sûr le faible couplage (ou notion d’indépendance) entre les différents modules et/ou groupe de modules, qui fait que l’on peut désactiver l’un d’entre eux, sans que cela est une incidence sur le reste de l’application.

Bien entendu, certains modules ont un degrés d’importance supérieur ; ainsi si l’on retire Mage_Catalog et Mage_Checkout de Magento, il ne reste plus grand chose…

2. En quoi la gestion de la modularité concerne les développeurs ?

Dès lors que l’on veut ajouter du spécifique à un projet Magento (hors partie graphique), on est obligé d’implémenter des modules ou d’en récupérer de la communauté. Il est alors fort probable (et cela, par expérience personnelle) qu’il soit nécessaire d’implémenter des fonctionnalités qui ne serviront peut être plus dans une version future, tels que :

  • Le cas d’un transporteur avec qui on travaille actuellement mais peut être plus ultérieurement
  • Le cas d’un logisticien qui assure la gestion des stocks, et si l’on veut en changer ?
  • …..

Autant de cas dans lesquels si cela est prévu à l’avance (ce qui n’est pas toujours évident), il est préférable de l’avoir anticipé au niveau du code, car au moment du changement de partenaire/SI, il sera possible de remplacer une brique de code par une autre via de la config, sans être obligé de passer par un long et fastidieux travail de recherche de dépendances dans tout son projet afin de savoir qui appelle le transporteur X ou le logisticien Y.

C’est sur la résolution de cette problématique, que je vais axer l’écriture de ce billet.

3. Comment la modularité est mise en place dans Magento ?

3.1 Séparation sur le filesystem

Le premier point important pour la mise en place de la modularité de l’application Magento est la séparation en dossiers spécifiques (modules) sur le filesystem :

magento-optimisez-la-gestion-modulaire-structure-filesystemCette séparation fonctionne sur le modèle suivant :

  • app/code/community/{NAMESPACE}/{MODULE}
  • app/code/core/{NAMESPACE}/{MODULE}
  • app/code/local/{NAMESPACE}/{MODULE}

où les dossiers community, core et local contiennent respectivement le code des classes des modules communautaires (récupérés depuis Magento Connect ou le site de l’éditeur), des classes du noyau Magento, et des classes des modules spécifiques développés pour le projet.

Le tag {NAMESPACE} est le nom du dossier qui est censé identifier le propriétaire du module. (Un nom de web Agency, d’un client spécifique….)

Le tag {MODULE} est, comme vous l’aurez compris, le nom du dossier qui identifie le module et qui doit être le plus pertinent possible par rapport au fonctionnel du module à implémenter.

Notez que la combinaison {NAMESPACE}/{MODULE} doit être unique pour éviter les conflits possibles de configuration avec d’autres modules.

Cette arborescence est un choix qui permet de séparer les composants les uns des autres pour, le cas échéant, être en mesure de les désactiver plus facilement.

3.2 Pattern event/observer

L’implémentation du pattern event/observer dans Magento est souvent perçue comme un moyen supplémentaire, en plus de la surcharge, pour modifier/ajouter du comportement à Magento.

Pour autant, il est bien question ici d’un point clé pour apporter un système modulaire à une application ; prenons un exemple à partir de deux modules du noyau Magento :

  • Mage_Catalog (qui à la responsabilité de gérer les produits et catégories)
  • Mage_CatalogInventory (qui à la responsabilité de gérer les stocks)

Vous en conviendrez facilement, d’un point de vue fonctionnel, à la sauvegarde d’un produit les stocks doivent être mis à jour.

Or, si à la sauvegarde d’un produit on considère le code suivant (ce code n’est pas exact mais est juste un exemple simplifié pour mon argumentation):

magento-optimisez-la-gestion-modulaire-implémentation-sauvegarde-produit-avec-dependance

On constate assez logiquement que dans la méthode save de Mage_Catalog_Model_Product (que j’ai réécrite pour l’occasion), un appel au Model cataloginventory/stock_item est effectué afin de mettre à jour les stocks.

Cette écriture induit donc une dépendance technique entre le module Catalog et le module CatalogInventory. Plus grave, si le module CatalogInventory est désactivé, cela plantera la sauvegarde du produit.

La solution choisie par Magento est alors d’écrire quelque chose qui ressemble à ça (ce code n’est pas exact mais est juste un exemple simplifié pour mon argumentation):

magento-optimisez-la-gestion-modulaire-implémentation-sauvegarde-produit-sans-dependance

A ce niveau, un événement avec une clé spécifique (ici arbitrairement : catalog_product_save) dans un contexte qui passe l’instance du produit courant et déclenchant le post, permet de relever le fait que si un module tiers, par exemple CatalogInventory, implémente un observer qui écoute cet événement, il sera possible de mettre à jour le stock mais sans écrire de dépendance directe entre le module Catalog et le module CatalogInventory.

Ce mécanisme est par conséquent quasiment indispensable à toute application qui se veut modulaire et a été implémenté dans Magento pour cette raison.

3.3 Le mécanisme de rendu de vue

Le troisième point mis en place par Magento pour permettre une application modulaire est son mécanisme de rendu de vue.

Tout développeur/intégrateur ayant travaillé sur la couche vue de Magento sait que celle-ci est divisée en trois parties :

  • layout (fichiers xml)
  • templates (fichiers phtml)
  • blocks (classes php)

A première vue cela peut paraitre lourd et on pourrait se demander quelles sont les responsabilités de chaque couche.

  1. Les blocks, ce sont des composants de vues et ce sont eux qui permettent de rendre du contenu sur une page via l’appel de la méthode toHtml. Ils doivent normalement contenir l’implémentation de méthodes utilitaires (pour la couche vue), et contenir l’implémentation de méthodes permettant de récupérer les données.
  2. Les templates, ce sont des fichiers phtml qui ne doivent gérer que l’affichage (structure du dom + données php). Ces fichiers sont associés à un block de type Mage_Core_Block_Template, ou dérivé, et contiennent l’affichage de ces blocks (puisque la surcharge de la méthode _toHtml d’un block de type Mage_Core_Block_Template fait peu ou prou un include sur le fichier de template)
  3. Les layouts, ce sont des fichiers (en général un par module) qui permettent de définir comment (sur quelle page ou plus précisément à l’appel de quel handle) seront disposés les blocks entre eux, en mémoire avec leur paramétrage associé.

Et oui, étant donné que le mécanisme de rendu de vue de Magento s’opère en 2 étapes :

  1. Chargement des blocks en mémoire (loadLayout)
  2. Affichage des blocks précédemment chargés en mémoire (renderLayout)

La pertinence de ce choix par rapport à la modularité est la suivante : si on désactive un module, le fichier de layout du module ne sera plus pris en compte, ces blocks ne seront donc plus mis en mémoire, et donc plus affichés. Tandis que si nous n’avions pas eu ce genre de mécanisme (ou un autre), à la désactivation du module, il aurait eut fallu supprimer de tous les fichiers de templates, toutes les dépendances sur ce module.

En dépit de sa lourdeur, ce choix d’implémentation permet à Magento d’être aussi modulaire sur la couche vue de son application.

4. Quelques ratés dans l’implémentation de la modularité

Magento repose certes sur l’implémentation d’un framework solide (Mage_Core et Mage_Page), et fortement inspiré de conventions du Zend Framework 1, mais il n’empêche qu’on trouvera quelques couacs par rapport à l’implémentation d’une modularité « parfaite ».

Parmi ceux-ci :

  1. Le module Mage_Adminhtml, ce module historique de Magento contient la partie admin de tous les modules du noyau Magento. Par essence, cela est contradictoire avec une bonne implémentation de la modularité, et en y regardant de plus près, vous pourrez vous apercevoir que Magento a corrigé le tir dans ses versions suivantes, comme avec la version 1.5CE, dans laquelle est apparu le module Mage_ImportExport qui implémente lui, sa propre ‘couche’ admin.
  2. « L’horrible » template catalog/product/price.phtml, qui fait > 400 lignes à cause de la combinatoire d’affichage de tous les prix incluant la gestion des taxes fixes. Eh oui ! L’affichage des taxes fixes gérées par le module Mage_Weee, n’a à priori rien à faire ici, puisque c’est une taxe dont tous les e-Commerçants ne se servent pas, et dont le module peut par conséquent être désactivé.
  3. Le template catalog/product/view/addto.phtml, pour lequel on notera une petite dépendance sur le module wishlist
  4. Dans la classe Mage_Catalog_Block_Product_Abstract, où on trouvera une dépendance mineure sur le module de checkout.

Globalement à part les exemples 1 et 2 (et peut-être d’autres que je n’ai pas dans l’immédiat en tête) dont les impacts s’avèrent significatifs, les autres « couacs » relatifs à la gestion de la modularité de l’application ne sont que mineurs.

Bonne nouvelle toutefois, Magento a fait un point là-dessus lors de précédentes conférences Imagine (de mémoire, en 2012 me semble t-il), et ces coquilles seront corrigées en version 2.X

5. Quelques exemples de conception pour lesquels la modularité est utile

5.1 Exemple 1 : conception d’un couplage entre un module Carrier et ERP

Considérons le besoin fonctionnel minimum suivant :

  1. Vous avez besoin d’un transporteur XXX qui prend en charge vos livraisons
  2. Vous voulez réaliser un export de vos factures vers un ERP

Techniquement, on pourrait considérer arbitrairement que vous alliez implémenter les modules suivants (à noter que l’on pourrait regrouper ces implémentations de modules dans des modules plus conséquents, mais ce n’est pas le sujet) :

  • Mycompany_CarrierXXX
  • Mycompany_ExportERP

et notamment si vous avez besoin d’exporter certaines informations relatives au transporteur, vous pourriez avoir le code suivant (notez que ce code n’est pas exact et qu’il représente une possibilité de ce qui pourrait être implémenté):

magento-optimisez-la-gestion-modulaire-implémentation-export-facture-avec-dependance

A ce niveau là, on trouve une dépendance entre le module CarrierXXX et le module d’ERP. De fait, si l’on veut supprimer le transporteur pour le remplacer par un autre, cela impliquerait de vérifier dans l’intégralité du code les dépendances à ce niveau.

Du coup plutôt que d’écrire une dépendance directe ici, la bonne solution serait de déclencher un événement pour lequel le module CarrierXXX implémenterait un observer.

5.2 Exemple 2 : draft d’un composant de gestion de promotion

Considérons le besoin fonctionnel suivant :

Vous voulez afficher sur votre page d’accueil la liste de toutes les promotions de votre catalogue ; et vous savez que dans Magento, les possibilités fonctionnelles pour faire des promos catalogue sont :

  • tier prices/custom group price
  • special prices
  • Utilisation du moteur de promotion catalogue Magento

En revanche, d’un point de vue du code vous savez que rien n’existe nativement dans Magento afin de vous permettre de récupérer la liste des produits en promotion.

Techniquement, nous allons partir sur la table catalog_product_index_price qui contient la liste des prix à jour pour déterminer les produits en promotions. Nous allons ensuite charger une collection de produits, et gérer cela dans un module Mycompany_Promotion (à noter encore une fois que l’on pourrait regrouper ces implémentations de modules dans des modules plus conséquents, mais ce n’est pas le sujet) :

magento-optimisez-la-gestion-modulaire-implémentation-recuperation-produits-promotion

A ce niveau là, on peut constater que la collection de produits va remonter tous les attributs produits flagués à ‘Used in product list’ à true et cela compte tenu de la condition ‘addAttributeToSelect’. Cependant ce n’est peut-être pas suffisant, et si l’on veut ajouter d’autres attributs sans les flaguer, ou que l’on souhaite faire des jointures sur d’autres tables (url rewrite, catalog inventory….), et bien en l’état nous serions obligés de modifier ce code qui se veut pourtant générique.

La solution est donc de déclencher un événement avant le return pour que d’autres modules puissent ajouter des attributs spécifiques ou manipuler la collection de produits à leur guise.

5.3 Exemple 3 : optimisation de la modification du statut de commande

Puisque Noël approche à grands pas et que notre hotte déborde déjà un peu ! Ce troisième exemple est un cadeau dans lequel vous retrouverez une implémentation qui permet de changer le statut d’une commande de façon « plus rapide ». En effet, la seule façon native dans Magento de changer le statut est de le modifier puis de sauvegarder la commande, ce qui peut être couteux en raison des traitement exécutés, voici donc le code :

magento-optimisez-la-gestion-modulaire-methode-mise-a-jour-statut-commande-cadeau-!Vous noterez à la fin de cette implémentation que j’ai déclenché un événement qui peut être écouté par d’autres modules pour intervenir à ce moment précis.

6. Conclusion

Au niveau de l’implémentation de son noyau, Magento a tout mis en place en terme de structure pour répondre à cette problématique de modularité.

En ce qui concerne les développeurs, ce qu’il faut savoir, c’est qu’une implémentation modulaire va nécessiter pas mal de conception pour être bien réalisée, et ce temps de conception n’est pas forcément toujours disponible en fonction des projets ni même toujours utile (pour des projets à faible besoin de fonctionnalités ajoutées).

En revanche pour tout projet qui requiert beaucoup de développements spécifiques, je pense que ce temps de conception n’est pas inutile pour :

  1. Rester dans l’esprit de développement du framework
  2. Prévenir des problèmes de structure, notamment avec des problèmes de gestion de dépendances dispatchées dans tous les modules…