La Lanterne Rouge

Warning: Geek Inside

WebHomeBank : une interface web pour HomeBank

- Posted in Sans catégorie by

WebHomeBank : une interface web pour HomeBank

Avertissement : cet article est long, très long, voire trop probablement. Donc si vous souhaitez le lire jusqu'au bout, préparez-vous un café (ou un thé, ou même une infusion), quelques biscuits, passez aux toilettes d'abord, et prenez un fauteuil confortable.

TL;DR: La version 1.0.0-beta de WebHomeBank est disponible sur mon GitHub. C'est une application web en PHP permettant de consulter les budgets créés avec HomeBank. Un Dockerfile est fourni avec le code source pour monter rapidement et simplement un container avec l'application. Ci-dessous un assortiment de captures d'écran pour avoir quand même un petit aperçu du résultat (langues FR et EN, thème par défaut et "moderne").

Mise à jour 17/11/2015 : une démo en ligne est disponible ici : http://webhomebank-demo.sloppy.zone/

WebHomeBank : une interface web pour HomeBankWebHomeBank : une interface web pour HomeBank

WebHomeBank : une interface web pour HomeBankWebHomeBank : une interface web pour HomeBank

WebHomeBank : une interface web pour HomeBank

WebHomeBank : une interface web pour HomeBankWebHomeBank : une interface web pour HomeBank

Ceci fait, rentrons dans le vif du sujet :)

Pour gérer mon très imposant patrimoine financier (ha ha), j'ai cherché fin 2013 une application de gestion de compte qui me permettrait de mieux tracer et surtout mieux prévoir et anticiper mes dépenses. Après un petit tour d'horizon j'ai finalement jeté mon dévolu sur Microsoft Money.

Ha ha vous y avez cru.

Non, pas du tout, j'ai plutôt essayé HomeBank, une application disponible sur toutes les plateformes (j'entends Linux, Windows et Mac) qui offre des fonctionnalités simples mais très pratiques, avec un look sobre et efficace. Je ne peux que vous conseiller d'y jeter un oeil si vous ne connaissez pas.

HomeBank version 5, très simple, mais très efficace

HomeBank version 5, très simple, mais très efficace

HomeBank version 5, très simple, mais très efficace

HomeBank version 5, très simple, mais très efficace

HomeBank version 5, très simple, mais très efficace

HomeBank version 5, très simple, mais très efficace

HomeBank version 5, très simple, mais très efficace

Après plus d'un an d'utilisation j'étais pleinement satisfait du service, l'application est stable, je n'y ai trouvé rien à redire et cela m'a permis de passer quelques périodes un peu délicates avec plus de sérénité grâce à la prévision des budgets en fonction des dépenses planifiées.

Cela étant, il faut bien avouer que le principe des applications dites "lourdes" peine de plus en plus à faire reconnaître ses mérites sur ce type d'application. On aimerait pouvoir consulter et modifier de telles données de n'importe où, depuis son smartphone ou même un navigateur chez un tier.

En regardant le format des fichiers de sauvegarde de l'application je me suis dit qu'il devrait être assez simple de monter une petite interface pour au moins consulter les informations à distance : la totalité des informations d'un "jeu de comptes" (ou budget) est stockée dans un seul fichier. Chez moi, ce fichier est stocké sur mon serveur Usul auquel j'ai accès via SSH et Samba sur mon LAN. Encore fallait-il que le format de ce fichier soit utilisable facilement.

Bingo, c'est du XML, et du genre avec une structure très simple.

Comme j'envisageais de pouvoir faire grossir petit à petit cette application pour qu'elle se rapproche au fur et à mesure de son modèle lourd, je me suis dit que partir sur du PHP-from-scratch comme je le fais (trop) souvent pour mes petits besoins persos, c'était pas vraiment la bonne approche. À l'inverse, partir sur un framework à la Zend ou à la Symfony, c'est carrément contre-productif.

J'ai donc cherché un petit framework simple à prendre en main, et surtout qui "juste marche" très rapidement pour monter un PoC, mais qui pourrait ensuite évoluer en une "vraie" application.

Ze Framework

Mon choix s'est porté sur... (roulements de tambours)... Fat Free Framework ! (F3 pour les intimes)

Il s'agit d'un micro-framework, en PHP bien sûr (oui je reste dans ma zone de confort quand même), qui tient dans 65 kio, et qui est - je peux le confirmer - extrèmement facile à prendre en main et à étendre. Il est totalement couplable à Composer, ce qui permet facilement de venir greffer à son projet de nouveaux modules simplement (j'y reviendrai).

Je ne peux décemment pas m'étendre sur ses fonctionnalités et sa puissance, mais conseille à tout développeur PHP qui a régulièrement des petites applications à coder de sérieusement le considérer comme une base pour la prochaine.

WebHomeBank : une interface web pour HomeBank

Allez, pour le plaisir, voici une petite liste des points forts du framework que j'ai pu constater personnellement, et après j'y reviens plus :

  • Arborescence de fichiers libre (*)
  • Faible empreinte mémoire
  • Système de routage simple et puissant
  • Extensibilité
  • Toolkit réduit mais couvrant la majorité des besoins (authentification, cache, log, sessions, bases de données, email, internationalisation, images, etc.)
  • Support de Composer (bon, aujourd'hui ce n'est plus vraiment rare)

Et il y en a encore beaucoup que je n'ai pas utilisés ! La liste complète est présentée ici : http://fatfreeframework.com/home

(*) Ce point est un avantage aussi bien qu'un inconvénient. J'avoue avoir restructuré plusieurs fois mon application avant d'arriver à un agencement qui me satisfasse. Pour éviter cet écueil, on pourra utiliser f3-boilerplate qui est une proposition de structure très efficace : https://github.com/vijinho/f3-boilerplate (il reste évidemment possible de l'adapter encore soi-même)

Seul petit bémol s'il doit y en avoir un : le code du framework est ultra-compact et manque donc un peu de lisibilité lors du debuggage.

Préliminaire : le parser

Avant de parler de zoulies zinterfaces en bon HTML proprement dites, il faut quand même monter les données du fichier dans une structure cohérente d'objets à laquelle on pourra accéder facilement. La première tâche a donc été de créer une classe dont l'objectif est de prendre un fichier en entrée, et de pondre un tableau PHP de valeurs à la sortie. Au vu de la structure du XML en entrée, ça n'a pas été très compliqué.

Une fois les données extraites, j'ai pu commencer à m'amuser avec.

Aperçu de la page d'accueil de WHB avec la liste des comptes

Aperçu de la page d'accueil de WHB avec la liste des comptes

La structure de données

Vient ensuite la création des classes métiers, appelées "models" dans la littérature (le "M" du modèle MVC). Dès cette étape, mes habitudes ont commencé à s'inquiéter de ne pas avoir d'objets dynamiques comme les Varien_Object dans Magento, et je me suis dit que créer autant d'attributs que de champs avec leurs paires de getters/setters (en français on dit accesseurs et mutateurs hein) ça allait m'amuser moyen. Surtout que je me doutais bien que ces éléments là allaient évoluer et que passer mon temps à faire leur refactoring allait surtout etre un bon moyen d'en perdre.

Bref.

J'ai craqué et après avoir jeté un oeil distrait à la classe Magic fournie par F3, j'ai finalement créé une classe MagicObject (qui est une sorte de Varien_Object simplifié).

Pour info, cette classe permet d'appeler des méthodes get/set/unset/isset grace aux méthodes magiques de PHP. Cela permet d'abord de ne pas avoir à déclarer chacun des champs en tant qu'attribut, et ensuite de pouvoir gérer assez élégamment les surcharges et les restrictions d'accès dans les classes enfants si nécessaire.

Tout n'est pas rose cependant : ce fonctionnement entraine une surcharge non-négligeable sur le CPU lors de l'exécution du code. Sur un seul appel c'est insignifiant, mais sur plusieurs centaines c'est quand meme un aspect à ne pas négliger.

Bon, pour moi c'est tout bénef'. J'ai l'habitude de travailler comme ça, et mon application ne manipulera que de faibles quantités de données, donc l'un dans l'autre je devrais m'y retrouver.

Parenthèse : "Pourquoi avoir réinventé la roue alors que F3 proposait une classe Magic similaire ?", me demanderez-vous. Je vous remercie, c'est une question très pertinente, je vais donc vous répondre : je souhaitais que le jeu de classes qui représente les entités de HomeBank soit totalement indépendant et puisse être réutilisé dans une toute autre application - éventuellement basée sur un framework différent. Je concède volontier que c'est une volonté de découplage poussée à l'extrême, mais je profite d'être sur une application personnelle pour laisser libre cours à mon perfectionnisme, même s'il est exagéré dans le contexte. Je n'ai pas cette liberté au boulot, et ça manque trop souvent.

J'ai donc créé les classes représentant les différentes entités de mon modèle (résumé ci-dessous, oui je sais c'est d'une complexité cosmique), et au départ tous les objets étaient créés avec leurs données à l'intérieur, c'est à dire que la totalité du fichier XHB contenant les comptes était chargée en mémoire dans les données des objets instanciés.

J'ai fait ça par facilité d'abord, et ensuite parce que je voulais voir les conséquences que cela pourrait avoir sur les performances et la facilité de manipulation par la suite dans l'application (comment gérer une liste d'éléments avec des filtres par exemple ?). Cela m'a permis d'avoir un PoC fonctionnel assez rapidement, même si dans le principe l'implémentation était très loin d'etre optimale (mais c'est un peu le principe du PoC hein).

Ça c'est au moins le diagramme de classe d'Ariane 5. Au moins.

Ça c'est au moins le diagramme de classe d'Ariane 5. Au moins.

Puis, bien après avoir monté la première version fonctionnelle de mon application, j'ai commencé à me demander quels seraient les bénéfices à monter ces mêmes données dans une base de données. L'idée était alors d'avoir une base SQLite par fichier, et à chaque requête de vérifier que la base était à jour par rapport au fichier XHB, et la reconstruire si ce n'était pas le cas.

Mais comme j'aime bien les complications, je voulais que le choix du stockage soit déterminé par la configuration, et soit donc dynamique. J'ai donc fait un énorme refactoring de mon modèle de données et j'ai séparé comme il se doit les modèles - qui sont donc les représentations des entités manipulées - de leur ressource - terme emprunté à Magento qui définit la manière dont les données sont chargées/enregistrées et qui comprend également la gestion des collections (listes d'objets d'une même entité). On parle de DAO dans le monde Java (Data Access Object).

Je suis donc arrivé à deux types de ressources qui pouvaient fonctionner en parallèle :

Memory : les données étaient chargées en mémoire dans les objets comme dans mon PoC, mais cette fois-ci avec une séparation nette entre modèles et ressources,

Db : les données étaient chargées dans une base, et grâce à l'intervention divine de PDO et du module zend-db du Zend Framework 2 (installé via Composer), la gestion des différentes bases de données est transparente du point de vue développeur.

Ce changement, même s'il implique un temps de traitement conséquent lors de l'initialisation de la base lors de la première requête (avant qu'elle soit dans cette sorte de cache), permet ensuite de diviser par deux les temps de chargement des pages subséquents. On bénéficie alors en plus de la puissance du langage SQL pour l'interrogation des données (filtrage, tri et chargement).

Le graphique de répartition des dépenses dans WHB

Le graphique de répartition des dépenses dans WHB

Quelques temps après, suite à l'implémentation de nouvelles fonctionnalités (comme le rapport de coût des véhicules), je n'ai pas eu la motivation de continuer à conserver l'équivalence entre les ressources Memory et Db et j'ai finalement retiré le support de la première, certaines opération sur les collections devenant trop lourdes à implémenter sans SQL.

Même si cela représente une quantité de code finalement importante, il faut bien se résoudre parfois à jeter ce qui ne sert plus !

Pour information, j'ai volontairement copié - en simplifiant fortement - de très nombreux aspects que l'on retrouve dans Magento : models, ressources, collections, etc. La force de l'habitude je vous dit ! J'admets volontiers sur ce point que la structure est exagérément complexe par rapport au besoin mais c'était assez fun à développer, et c'était bien le but. D'autant que comme cette structure est relativement générique et propre, je pourrai me resservir du principe pour toute nouvelle application à venir.

Pour information également, j'aurais pu passer par le SQL Mapper fourni par F3, mais j'ai eu peur de me retrouver bridé à terme et ai donc préféré sur ce sujet m'écarter un peu de la ligne définie par le framework en réimplémentant une solution que je connaissais déjà (et en plus, c'est plus drôle ^^). Il est très probable qu'il soit possible d'obtenir peu ou prou la même chose avec ce SQL Mapper que ce que j'ai fait avec Zend.

L'architecture générale

La force de F3 tient en sa simplicité et en son efficacité, je le dis et le répète. Cependant il ne faut pas non plus s'imaginer que cela permet de résoudre tous les problèmes, et que cela puisse faire face sans encombre au refactoring continuel d'une application en cours de réalisation. Si on veut répartir correctement les responsabilités entre les composants et faciliter leur restructuration future, il reste conseillé d'utiliser ce framework comme une base (solide), et de poser quelques fondations supplémentaires au-dessus.

(Ici, j'enfile ma cape d'architecte.)

En ce qui me concerne, pas de surprise, j'ai réutilisé des concepts provenant encore une fois de Magento (et donc plus ou moins de Zend).

Je prends un exemple tout bête pour illustrer : dans F3, la première ligne de code utilisateur qui sera exécutée au traitement d'une requête est le constructeur du contrôleur, puis sa méthode beforeRoute() - si elle est définie.

Si vous souhaitez effectuer des traitements préliminaires à chaque exécution et que ces traitements sont à chaque fois les mêmes, il vous faudra par exemple les placer dans une classe à part et penser à les appeler depuis l'une des deux méthodes précédentes depuis tous les controleurs que vous créerez. Ça marche, ce n'est pas excessivement moche, mais ça fait quand même du code dupliqué. Et la conséquence du point précédent est que si vous avez besoin plus tard d'appeler un deuxième traitement différent, il va falloir refaire la même démarche pour tous vos contrôleurs.

Pour cette raison (et d'autres, mais je résume), j'ai créé un ensemble de trois classes collaboratrices qui donnent un cadre à l'application - et au développeur :

  • AbstractController : le nom dit à peu près tout, il s'agit de la classe mère de tous les contrôleurs, qui définit un constructeur en final, mais met à disposition des classes filles une méthode _init(). Elle permet de centraliser de très nombreuses fonctionnalités en fournissant une implémentation par défaut, mais dont la plupart peuvent être surchargées si nécessaire.
  • Main : classe utilisée comme singleton qui initialise quelques éléments de bas niveau mais qui sert surtout d'interface pour accéder à l'instance de la classe suivante.
  • App : classe représentant "l'application" en elle-même et devant être surchargée par la classe réelle définie dans la configuration. Elle fait le lien avec d'autres classes composant ma surcouche maison : Session et Cache. Le développeur peut y ajouter tous les bouts de code et mettre à disposition des autres classes toutes les méthodes qu'il jugera nécessaire.

Grâce à ce trio, tout appel à un contrôleur héritant de AbstractController exécutera automatiquement les procédures définies dans l'application App en passant par Main. Le développeur garde ainsi la main sur l'implémentation exacte, tout en pouvant se reposer sur certains mécanismes par défaut pour son confort.

Le graphique de suivi du solde dans WHB

Le graphique de suivi du solde dans WHB

Dans cet esprit, j'ai ajouté ma propre surcouche sur le système de vue fourni par F3 pour pouvoir manipuler plus souplement les éléments de l'interface (et gérer le cache, mais j'y viendrai plus loin).

Dans Magento, chaque zone de la page est appelée "bloc" et peut être gérée par une classe PHP spécifique qui s'appuiera ou non ensuite sur un template (un .phtml) pour le rendu. L'ensemble des blocs est géré via un système de layout défini en XML, extrèmement souple et puissant, bien que très rebutant quand on le découvre.

Reprendre le système de layout n'était pour moi ni nécessaire ni souhaitable, cela rajouterait simplement une complexité conséquente, qui plus est inutile à ce stade. Mais j'ai décidé d'extraire une version simplifiée des "blocs" en leur ôtant cependant la possibilité de reposer sur une classe propre.

La différence majeure avec le système fourni par F3 est donc que mes blocs sont définis depuis les contrôleurs avec un nom (unique), peuvent être paramétrés finement, et sont ensuite inclus dans le template de leur parent via ce même nom, et non directement en appelant la méthode render() sur l'instance de la classe View du framework. L'objectif étant d'éviter au maximum les traitements dans les templates qui compliqueraient la maintenance et la lisibilité (un peu le principe du MVC hein).

Si vous aviez des doutes je le précise : j'ai créé une sorte de framework par-dessus le framework qu'est déjà F3, mais ce dernier reste clairement une aide capitale pour ne pas avoir besoin de réinventer la roue pour des besoins qu'on pourrait qualifier injustement "de bas-niveau", c'est à dire à mon sens des besoins de base que l'on devrait retrouver dans toute application web.

Les petits zigouigouis

Mais je ne me suis pas arrêté aux classes Main, App, et à la surcharge de la classe View de F3. Je me suis basé sur le reste du framework à disposition pour broder d'autres fonctionnalités utiles. Allons-y pour la petite liste.

J'ai tout d'abord créé la classe I18n, qui comme son nom l'indique pour tout développeur, gère l'internationalisation : principalement traduction et formatage lié à une locale. Grâce au système de traduction intégré à F3 il est très facile de gérer des packs de traduction pour chaque langue existante. Je l'ai simplement un peu adapté à ce niveau pour faciliter son utilisation, et j'ai ajouté les méthodes adéquates pour gérer le formatage des nombres, des dates et des représentations de monnaies (un peu indispensables pour moi ici !). Je me suis reposé pour cela sur l'extension PHP intl, qui devient donc obligatoire pour faire fonctionner WHB.

Conscient que les temps de traitement imposés par le chargement de l'ensemble des données du fichier XHB en mémoire alourdissaient fortement l'application, j'ai commencé à réfléchir peu après l'achèvement d'une première version fonctionnelle de l'application à une mise en cache optimisée des éléments lourds de chacune des pages.

C'est en dérivant de cette idée que le système de "blocs" s'est imposé, toujours en m'inspirant fortement de ce qu'on trouve dans Magento. Au départ cela permettait à chaque contrôleur de stocker la sortie de chaque bloc avec un identifiant dépendant des données utilisées pour sa génération : à l'appel suivant, si les données n'avaient pas changé l'identifiant était le même et donc le cache existait pour ce bloc, ce qui permettait une réutilisation immédiate, sans calcul supplémentaire.

Le résultat était encourageant, et la page de la grille des opérations a vu ainsi son temps moyen d'affichage divisé par trois. C'est bien. Mais je me suis dit qu'il pourrait sûrement être possible de faire mieux.

Évidemment.

Le rapport de coût des véhicules dans WHB

Le rapport de coût des véhicules dans WHB

Dans la version Enterprise de Magento il y a une fonctionnalité très utile pour les sites à fort trafic : la mise en cache de pages complètes (FPC, pour Full Page Cache).

Cette fonctionnalité phare il y a quelques années perd à présent un peu de son attrait face à la montée en puissance de solutions de reverse-proxy de cache dédié tels que Varnish, mais le principe est très intéressant et assez efficace, même s'il impose ses propres contraintes inhérentes à son fonctionnement.

J'ai donc très honnêtement plagié ce fonctionnement et implémenté ma version, que j'ai nommée Request Output Cache (ou ROC, quand on n'a pas le temps). Bien entendu, là également il s'agit d'une version extrèmement simplifiée du FPC de Magento.

Le principe est très simple et consiste - avant de transférer l'exécution du traitement de la requête au contrôleur (désigné par le routeur) suite à l'appel d'une URL - à interroger le cache, au cas où il aurait déjà la page complète à renvoyer.

Si cet appel réussit, on renvoie simplement le HTML du cache. Dans le cas inverse, on appelle le contrôleur pour traiter la requête et avant de renvoyer la page HTML résultante on la stocke dans le cache en y associant évidemment son URL et les différents paramètres ayant servi à la générer. De cette manière si la page est déjà dans le cache à l'appel suivant les seuls traitements qui sont effectués sont l'extraction du contenu du cache et le renvoi.

Bien sûr, c'est une vision simplifiée, car de nombreux éléments d'une page ne peuvent pas être stockés de manière brute dans la page car ils dépendent de la session de l'utilisateur ou d'une action qu'il vient d'effectuer (l'affichage d'un message de confirmation par exemple) ou d'un calcul dynamique qui n'est pas lié aux paramètres ayant servi à générer la page (sur WHB il s'agit par exemple du temps de génération affiché dans le footer).

Il a donc fallu améliorer un peu ce fonctionnement en y ajoutant la gestion des punch holes (aussi appelés ESI : Edge Side Includes). Il s'agit d'identifier dans le contenu généré (la page HTML dans notre cas) des sections - en fait des blocs - qui ne doivent pas être rendus tels quels au sortir du cache mais qui doivent être remplacés par du contenu recalculé.

Pour cela on les entoure d'une paire de balises spéciales qui sera détectée lors de l'extraction du cache et qui permettra de savoir quel callback appeler pour générer le contenu qui devra être placé à l'intérieur. Par défaut, le callback sera simplement de générer le rendu du template configuré pour ce bloc (il faut donc au préalable que ce bloc ait été configuré, ce qui peut être fait dans les méthodes _beforeRoute() ou _{action}ActionBefore() qui sont à implémenter par le contrôleur).

Si bien sûr une page ne peut pas être mise en cache (par exemple car le nombre de contenus possibles est trop élevé et génèrerait donc trop d'échecs lors de l'interrogation du cache), un flag est disponible sur le contrôleur pour l'indiquer.

Précision : j'ai pris dans chacun des exemples une page HTML, mais rien n'empêche de stocker dans mon "ROC" d'autres types de données. Je m'en sers aussi bien pour stocker le JSON qui sert à générer les graphiques sur les différentes pages via des appels AJAX. La gestion des punch holes n'est par contre implémentée que pour des requêtes de type text/html.

WebHomeBank : une interface web pour HomeBank

Pour analyser les goulets d'étranglement et les performances générales de mon application qui ont abouti à l'implémentation des fonctionnalités précédentes (et à leur validation), je me suis reposé sur un outil indispensable à tout développeur PHP : le profiler de Xdebug.

C'est avec lui que j'ai pu notamment détecter des chargements de données dupliqués, des opérations gourmandes injustifiées, et plus généralement pour trouver des pistes pour accélérer le rendu des pages. Je l'avoue très franchement : quand on code sans tester rigoureusement, on fait invariablement des boulettes d'antologie.

En poursuivant sur cette lancée, la prochaine étape serait d'ailleurs d'implémenter des tests unitaires avec PHPUnit. Pas que ça soit vraiment critique sur une application de ce type, mais ça serait une bonne pratique de plus mise en oeuvre et - je n'en doute pas un instant - un bon moyen de corriger encore une pelletée de bugs résiduels.

Cela me permettrait également de découvrir ce que F3 a prévu pour simuler le fonctionnement d'une application et les procédures de test à disposition.

WebHomeBank : une interface web pour HomeBank

Le container d'exécution : Docker à la rescousse

L'objectif de mon application était évidemment avant tout personnel. Il s'agissait de pouvoir accéder à mon budget stocké sur mon serveur Usul depuis un navigateur externe.

L'utilisation de ce serveur imposait cependant une contrainte assez... heu, contraignante (si si). Il tourne en effet sous Debian 7 (Wheezy), qui ne fournit que PHP 5.4 en standard. Je souhaitais pour ma part ne pas dépendre d'une version aussi ancienne et bénéficier de certaines fonctionnalités de la version 5.5 (finally, déréférencement de tableaux, etc.), voire 5.6 si besoin.

Installer une version hors dépôts officiels sur ma Debian n'était pas envisageable. Monter mon serveur en version vers Jessie non plus, du moins pas pour le moment (il faudra pourtant que je m'y résolve un jour).

À côté de cela, je m'intéresse de plus en plus à Docker et son système de container/virtualisation légère. Je l'utilise un peu à la maison sur Leto mais surtout au boulot où je profite de ces containers "jetables" pour tester différentes versions d'applicatifs (MySQL, Redis, Memcache, etc.), sans pourrir mon système Archlinux qui fonctionne plutôt bien.

Pourquoi dans ce cas ne pas faire tourner mon application dans un container Docker avec la bonne version de PHP ?

Il m'a fallu pour cela installer Docker sur Usul selon la documentation fournie. La seule contrainte que cela a nécessité est l'installation d'un noyau plus récent depuis le dépôt wheezy-backport (3.16 à la place du 3.2). J'avais un peu peur, mais tout s'est bien passé et je n'ai à déplorer aucun dysfonctionnement depuis cette mise à jour.

Une fois Docker installé, j'ai mis en place un registry afin de faciliter le transfert des images à jour depuis mon poste de développement (Leto) vers le serveur. Je peux ainsi jouer avec des

docker push

et des

docker pull

pour mettre à jour facilement et rapidement le container qui fait tourner WebHomeBank. L'image décrite par le Dockerfile est basée sur php:5.6-apache et y ajoute simplement extensions PHP et modules Apache nécessaires au bon fonctionnement de mon application.

J'utilise enfin le système de montage de fichier fourni par Docker pour faire pointer un chemin précis dans le container vers le fichier XHB situé sur l'hôte. De cette manière il n'est pas nécessaire de modifier quoi que ce soit dans le container après son lancement, il suffit de lui donner les bons paramètres lors de son premier démarrage.

Un petit bémol : cela nécessite que l'UID/GID faisant tourner Apache dans le container (33/33 sur une Debian) puisse accéder au fichier XHB. Dans mon cas cela signifie que j'ai dû autoriser la lecture dudit fichier à tout utilisateur, car changer son groupe ne me semblait pas être une meilleure solution. Une feature-request est ouverte pour assouplir ces points au niveau de Docker mais pour l'instant elle n'est pas sur la roadmap. Des alternatives semblent exister mais franchement, je ne m'y suis pas attardé.

Enfin, comme le port 80 du container doit être mappé sur l'hôte, et que je ne souhaite pas multiplier le nombre de ports HTTP ouverts sur mon serveur, j'ai préféré ajouter une règle de reverse-proxy sur mon Apache. Cela me permet en plus de bénéficier du SSL qui y est déjà configuré.

Suite à cette mise en place, j'ai dû revoir dans mon application bon nombre d'aspects dans la gestion des URL et de l'accès aux ressources externes (JS, CSS) afin que WebHomeBank puisse fonctionner aussi bien en accès direct que derrière un reverse-proxy, même si celui-ci fonctionne en HTTPS alors que l'application en elle-même est en HTTP.

Ça n'a pas été simple mais le fonctionnement actuel me semble correct. Le reverse-proxy doit simplement informer via les headers sous quel protocole il sert les requêtes entrantes afin que WHB (et F3, qui gère une bonne part de cet aspect) puisse générer des URL correctes pour le navigateur client.

Conclusion

Malgré son utilité toute relative, le développement de cette application m'aura permis de me replonger un peu dans l'architecture en PHP, appliquer mes connaissance liées à Magento et réimplémenter certaines fonctionnalités - ce qui permet d'en saisir encore mieux les subtilités et les difficultés qu'en étant simple utilisateur - et surtout découvrir le framework très intéressant qu'est F3. J'ai également pu utiliser Composer (ainsi que npm et Grunt, dans une moindre mesure) pour gérer les dépendances d'une manière plus souple, et - il faut bien l'avouer - simplement plus contemporaine.

Je passe sur de nombreux autres points : la tentative de création d'un thème responsive avec Bootstrap et LESS, puis finalement Foundation et SASS (voir le thème "modern" disponible dans l'application actuellement) qui m'a donné pas mal de fil à retordre et qui n'est toujours pas responsive (hé, je suis pas intégrateur CSS hein), le refactoring permanent de nombreuses classes et points d'architecture, la sensation de toujours en faire trop et vouloir tout trop générique, les galères habituelles avec la gestion des dates en PHP et en Javascript, ... bref, il faudrait un livre entier.

Cela dit, j'ai à présent couvert un spectre suffisamment large avec ce développement pour me lancer dans une autre application qui me tient à coeur mais dont je repousse le développement depuis plus d'un an à présent. J'espère pouvoir utiliser les fondations de WebHomeBank pour donner une base suffisamment solide à cette future application et ne pas perdre trop de temps à créer les briques élémentaires.

C'est un espoir un brin optimiste mais j'y tiens, on verra bien...

Je rappelle l'adresse du dépôt GitHub contenant les sources sous AGPLv3, librement forkables et modifiables donc : https://github.com/nanawel/webhomebank