Doc d'architecture à construire

Il est peut-être encore trop tôt, mais est-ce prévu de rédiger et dessiner (il me semble indispensable qu’il y ait des schémas, visuels), la doc de la nouvelle architecture. Je veux parler ici de documentation interne de l’architecture, c’est-à-dire pas la doc de migration ou d’intégration (comment utiliser), mais comment fonctionne le nouveau noyau.

Les buts, car on ne documente pas pour documenter (l’ordre est important, car il me semble qu’avant de penser à rameuter du monde, il faut déjà arriver à pas perdre celleux qui connaissent déjà) :

  • arriver à raccrocher les morceaux des contributeurices qui comprenaient au moins « un peu » comment ça marchait avant, quel était l’ordre d’exécution, et les dossiers/fichiers importants où se trouvait telle ou telle fonctionnalité noyau, afin de pouvoir de nouveau mettre le nez dedans de temps en temps, et plus généralement comprendre ce qui se passe
  • expliquer aux nouvelles personnes extérieures en quoi on utilise tels ou tels modules ou design patterns connus, et comment c’est utilisé concrètement chez nous, afin qu’une personne connaissant les bonnes pratiques modernes sache rapidement comment contribuer à SPIP

Afin de ne pas faire reposer 100% du travail sur deux personnes, il pourrait être intéressant de travailler par interviews (orales ou écrites, à déterminer), poser des questions très concrètes, et que la personne posant une question rédige plus formellement la réponse et fasse des schémas. Ensuite ça doit être relu et corrigé par les quelques qui savent, avant d’être publié dans une doc officielle.


Illustration d’exemple : si on part d’une connaissance de l’ancien SPIP, et qu’on essaye de suivre les commits et PR de refonte, et qu’on lit des phrases comme

Compiler le conteneur fallback si le conteneur complet n’est pas précompilé

ou bien

Une classe pour résoudre le contrôleur via le conteneur courant du Kernel

et autres joyeusetés du même genre, je ne pense pas me tromper en affirmant que c’est au mieux abscons pour l’immense majorité des gens qui sont déjà là, et qui voient passer tout ça en se demandant légitimement « est-ce que j’arriverai encore un jour à mettre le nez dedans et avoir quelque chose d’utile à en dire ? » :nerd_face:

Il ne s’agit bien sûr pas de réécrire en doublon ce qui existe ailleurs, mais faire des schémas et explications circonstanciées de qui-que-quoi dans quel ordre ça se lance chez nous, avec des liens bien sentis ailleurs pour chaque point important.


(Oui j’ai déjà lu en diagonal Compiling the Container (Symfony Docs) mais c’était un exemple flagrant parmi des dizaines d’autres pour illustrer :stuck_out_tongue: , notamment le fait qu’il ne faudrait pas avoir à lire des centaines de pages de doc ailleurs en anglais pour comprendre rapidement le fonctionnement global chez nous, et seulement aller ailleurs quand on veut approfondir un point précis)

1 « J'aime »

Alors… on fait déjà ce qu’on peut coté doc : il y a déjà bien plus de doc de migration que pour n’importe quelle autre version précédente de SPIP qu’il a pu exister dans le passé je crois bien ; et par ailleurs, il n’existe pas vraiment de doc d’architecture du code actuel 4.4 non plus nul part (à part des bouts rédigés sur Programmer par bibi tant bien que mal)…

Mais aussi ça évolue encore, car au fil des avancées on se retrouve avec de nouveaux problèmes, et c’est d’une bonne complexité (je trouve) de démêler tout ça. Cela dit on avait envoyé un dossier « Architecture » Architecture SPIP 5.0 - SPIP 5.0 - Documentation technique dans la doc récemment. Et on continuera de la faire évoluer et compléter au fur et à mesure de certaines stabilités.

Cette doc globalement sera à rendre plus humaine (plus résumée à des endroits, plus détaillée dans d’autres) mais elle a le mérite d’exister au moins et déjà un peu relue quand même.


Sur la problématique « Container » (fallback / complet), je vais tenter d’y répondre car c’est un peu tricky.

1) Conteneur

Déjà, ce qui est appelé « Container », ou Conteneur ou parfois « le DI » donc, est un « Conteneur de service » , au sens le plus habituel dans le monde PHP et symfony (psr/container, symfony/dependency-injection). On y déclare des services (ex: un logger), et quand on fait $container->get('logger') : le service est instancié 1 première fois au premier appel (tout ce qui est dans le conteneur est « lazy » par défaut, c’est à dire pas instancié s’il n’est pas réellement utilisé ; et 1 seul sera instancié à l’utilisation, un peu comme un singleton) ; Si le logger nécessitait pour être instancié d’autres dépendances (monolog par exemple), elles seront gérées automatiquement (c’est le côté « DI » : l’injection est automatique) : tu n’as pas à faire je sais pas $logger = new Logger(new Monolog()) toi même. Et surtout chaque service reste lazy et instancié 1 seule fois (par défaut du moins).

2) Conteneur compilé

Ce qui est appelé conteneur compilé est une étape dans symfony/dependency-injection : une fois que tu as défini tous les paramètres et services qui doivent rentrer dans le conteneur (c’est un calcul qui peut être coûteux, car cela passe par des passes de compilations, d’analyse d’attributs #[XXX], etc), tu en fais un cache compilé : c’est ce cache que tu reliras aux hits suivants → ultra rapide / performant.
Et il y a des moyens de vérifier sa fraîcheur sur certaines actions (chez nous par exemple sur var_mode=recalcul), et de l’invalider s’il n’est pas frais (ie: si des fichiers de déclaration de services qui ont servis à créer le conteneur ont étés modifiés).
Une fois que le conteneur est compilé (où que c’est lui que tu utilises), tu ne peux plus faire certaines actions dessus : notamment tu ne peux plus déclarer de nouveaux services dessus, en tout cas ça devient limité, et c’est assez logique.

3) DI et HttpKernel dans symfony standard

Tu passes de « conteneur » (builder) à « conteneur compilé » s’il n’existe pas de façon transparente car symfony connaît tous ses services / paramètres sans avoir besoin d’exécuter / démarrer l’application ; En gros tu as sur un hit http : le Kernel boot (il crée son conteneur ou lit le cache), puis à un moment après ça va faire qqc comme $response = $kernel->handle($request) (j’ai plus exactement en tête), mais c’est dedans que vont se faire toute l’analyse de la requête (Get Post Cookies, le choix des routes, controleurs et réponses envoyées), et par l’intermédiaire de « listeners » (un peu comme nos pipelines) (kernel.request, kernel.response notamment) tu peux agir à différents endroits : c’est partiellement décrit là dedans The HttpKernel Component (Symfony Docs)

4) DI de symfony ET donc le httpKernel de symfony dans SPIP

Depuis une PR récente. On avait fait cela en gros

  • boot de SPIP (inc_version.php)
  • puis le httpKernel donc handle(request) avec quelques listeners

Le problème est que inc_version.php, ce qui boot SPIP (et va analyser les plugins), fait beaucoup de choses aussi autour du cycle HTTP, en faisant des traitements et actions autour de $_GET, $_POST, etc : et nous on veut que tous ces éléments soit dans la Request de symfony créée (sinon on n’arrivera jamais a avoir un cycle propre Request → Response).

  • Donc on voudrait accéder Request dans des morceaux chargés par inc_version.php et dans des fichiers d’options
  • On veut que les plugins puissent déclarer des paramètres et services au conteneur builder.

Plus récemment autre PR, on a tenté pensant bien faire une autre solution

  • plutôt que de boot inc_version avant le handle(request), on le boot dans le cycle HTTP, dans un listener précoce
  • ça paraissait prometteur (pour le coup on peut modifier la Request), mais on se retrouve avec d’autres problèmes

5) on ne peut pas avoir un conteneur → conteneur compilé

Étant donné que SPIP doit démarrer inc_version.php, (qui va utiliser des éléments du conteneur) : si le conteneur n’est pas encore compilé (ou en absence de cache des plugins) on va devoir déclarer les paramètres & services & pipelines (transformés en event listeners) des plugins au conteneur builder, en démarrant SPIP (comme il fait habituellement) avec ses fichiers d’options (qui vont influer _request, set_request => la Request pour certains)

Pour que ça marche partiellement il faut un conteneur minimal / fallback incorrect pour la suite du hit une fois le conteneur complet prêt, et faire un « remplacement » de l’un par l’autre pour la suite du hit.

Je passe les détails mais qu’on ait inc_version.php avant handle(request) ou dans handle(request) : ça ne va pas en l’état du code qu’on a.

SPIP doit démarrer pour savoir 1) les plugins actifs, 2) les pipelines et autres joyeusetés de ces plugins ajoutés par les fichiers _options.php notamment. C’est là où arrive le problème : on devrait démarrer (hors cache) un conteneur et conserver le même jusqu’à la compilation. mais on ne peut pas.

Tout cela est tricky car SPIP dans son boot mélange plusieurs choses : l’analyse des plugins / pipelines, initialisations de constantes / globales ET des actions sur hit HTTP courant.

6) Solution envisagée à tester

Pour faire face à ce problème on va certainement devoir découper plus finement le boot (et faire d’autres BC break malheureusement certainement). L’idée que j’aimerais tester est la suivante :

  • un moyen de boot juste pour générer le cache des plugins et services, sans autre choses relatives au cycle HTTP, et notamment SANS charger les fichiers d’options qui font des actions runtime indésirables : on ferait CE boot (qui créera le conteneur compilé) avant le handle(request), le reste du cycles HTTP serait traité en listeners.
  • les fichiers d’options seraient chargés dans le cycle HTTP donc (listener précoce), mais ils ne pourraient plus modifier au runtime (à l’exécution donc) des éléments relatifs à la conf du conteneur (surcharges de constantes _DIR, ajout de pipeline via $GLOBALS['spip_pipeline], et peut être d’autres trucs que j’ai pas en tête)
  • déclarer des pipelines passerait uniquement par paquet.xml ou mieux le nouveau config/services.php + les attributs #[AsPipelineListener]
  • plus possible d’avoir un pipeline (listener) ajouté au runtime en fonction de condition de la request (if _request('truc')) { $GLOBALS['spip_pipeline']['truc'] .= ... }) : dans ce cas il faut déclarer le pipeline systématiquement, et tester le côté runtime dans le listener / pipeline.

Bon, je sens bien que ça ne va pas être aussi « simple » (huhu) que sur le papier, mais si on veut pouvoir profiter plus serainement du cycle http de symfony et du conteneur performant, on va devoir passer par là.

1 « J'aime »

Merci beaucoup pour cette explication particulière concernant les problématiques rencontrées sur les conteneurs, je comprends beaucoup mieux les enjeux sur ce point maintenant (et je ne dois pas être le seul). C’est clairement un truc qui devrait finir dans une doc d’architecture, enfin au moins en partie, car c’est seulement la solution finale choisie qui devra l’être. Cependant bon, c’était un point particulier d’illustration, mon propos valait pour toute l’archi du noyau. :slight_smile:

Mais ça montre bien qu’en interviewant sur des points très concrets, on peut avoir une explication compréhensible par presque tout le monde, petit à petit. (Mais il faudrait pas que ça se perde dans le forum, à rester là introuvable dans le futur.)

il y a déjà bien plus de doc de migration

ça je sais bien, mais c’est pour ça que j’ai fait un sujet différent, car je voulais évoquer précisément tout ce qui n’était pas la migration et l’utilisation (comment faire ceci cela, utiliser les nouveaux pipelines, la nouvelle délaration de cron, etc), mais plutôt la compréhension des mécanismes, des ordres d’appel, etc.

Cela dit on avait envoyé un dossier « Architecture »

Alors je n’ai pas du tout pensé à regarder ce nouveau site, car dans ma tête n’était pour lire plus facilement la doc de migration qui avait été découpée en plein de pages (versus le gros MD avant). Comme tu disais :

Le fichier UPGRADE_5.0.md aurait des liens pointant vers ce dépot de doc. Ça permettrait de séparer différents aspects de la migration en autant de plus petits fichiers.

J’ai du coup de gros problèmes avec ce nouveau site, mais ça serait plutôt dans le groupe documentation à discuter : doc.spip : quel domaine, et contenu