Gérer les actions sous forme d'objets
Idée qui me trote depuis un certain temps, mais que je pensais totalement impossible sans un réécriture complète du moteur, ou du moins sans casser la rétrocompatibilité avec les actions existentes. Mes réflexions récentes m'ont tout de même fait trouver une solution, que je me permets donc d'exposer ici. --
LordFarquaad
Idée de départ
Ce qui existe
Actuellement, les actions (tout comme les handlers et les autres modules) sont gérés comme de simple fichiers à inclure sauvagement à partir de la méthode
IncludeBuffered?, dont le principe consiste à demander au module d'afficher tout, et à récupérer ce qu'il a affiché pour le retourner. En gros,
WikiNi fait sans cesse un jeu de passe-passe en redirigeant ce qui est affiché vers un buffer (avec les fonctions
ob) et en récupérant le contenu comme retour de méthodes, bien souvent pour être affiché à nouveau juste après etc.
Les inconvénients de ce qui existe
Cette solution n'est peut-être pas idéale, mais on peut dire qu'elle fonctionne plutôt bien. Cependant, au niveau des actions, elles posent quelques petits problèmes/limitations:
- les paramètres passés aux actions doivent être stockés dans le noyau, et récupérés avec la méthode GetParameter, ce qui n'a pas beaucoup de sens...
- les actions doivent déterminer a priori tous les paramètres possibles, il n'est pas possible de permettre de passer des paramètres variables: par exemple, il n'est pas possible (ou du moins il serait assez difficile) de créer une action qui puisse prendre n'importe quel nombre de paramètres. Ceci est généralement contourné en passant un seul paramètre avec plusieurs valeurs séparées par des virgules. Il n'est pas non plus possible pour une action de savoir le nombre de paramètres qui lui ont été passés.
- certainement le plus problématique: les actions n'ont aucun espace de nommage des variables "propre" à elles seules. Lorsqu'une action a besoin de stocker des informations pour un appel ultérieur, ses seuls options sont de définir une nouvelle variable dans le noyau ou une nouvelle variable globale, ce qui n'est pas très propre... Quelques actions font cela, voir notemment:
- DiscussionsActionFootNote?
- (à compléter, je ne pense pas que ce soit la seule)
La solution
Pour contourner ces problèmes, la solution consiste à charger les actions sous forme d'objets, qui seront utilisés ensuite à la demande via une méthode spécifique. Les paramètres passés aux actions deviennent un paramètre de la méthode, et les actions ont leur propre espace de nommage (variables internes de l'objet).
Ses avantages cachés
- il devient inutile d'inclure plusieurs fois le même fichier pour exécuter les actions. Une seule inclusion suffit pour définir la classe.
- le fichier de chaque action n'étant plus appelé qu'une seule fois, il devient inutile de faire des tests pour vérifier si une classe ou une fonction auxiliaire a déjà été définie. Lors du premier appel, la classe est définie et instanciées, les appels suivants se font uniquement sur l'instance créée.
- il devient possible de rendre l'action header (ainsi que footer) interractive ! En effet, pour peu que l'entête ne soit plus chargée en premier (ce qui ne sert à rien), l'action header pourrait être appelée comme une simple action afin de modifier le comportement de l'entête, notemment pour spécifier un titre différent, modifier les meta etc. Ceci fera certainement l'objet d'une prochaine page, pourquoi pas NePasChargerHeaderEnPremier.
Ses inconvéniants
- cela peut paraître plus compliqué d'avoir à utiliser des objets, mais en général ça a tendance à structurer le code et donc à l'éclaircir.
- ... ?
Principe de fonctionnement
L'idée est simple: lorsqu'une action est appelée pour la première fois, on charge la classe du même nom et on en crée une instance qu'on stocke dans un vecteur des actions (paires "nom de l'action" => objet) et dans une variable
$action. Si l'action a déjà été appelée, on récupère
$action dans le vecteur des actions.
Ensuite, on appèle une méthode spéciale de
$action avec comme paramètres un vecteur des paramètres fournis par l'utilisateur (sous la forme clef => chaine) et l'objet
$wiki.
Ainsi, l'objet représenté par l'action est stocké tout au long de l'exécution et peut mémoriser ses propres variables internes.
(
NB.: pour l'implémentation, faire attention au risque de copie des objets en
Php4?)
Rétro-compatibilité
Si on veut conserver la compatibilité avec toutes les actions existentes, surtout les
ContributionsWikiNi, il faut permettre aux anciennes actions de continuer à fonctionner sans avoir besoin d'être transformées en objets. Pour cela, il suffit de définir une nouvelle nomenclature des actions implémentées sous forme de classes. Ce sont des classes, dans des fichiers à inclure... Je propose simplement
nomdelaction.class.inc.php, ce qui permettra au moteur de les distinguer des anciennes actions, nommées simplement
nomdelaction.php.
Cas de test
Cette section définit le comportement du noyau de
WikiNi lors de l'appel d'une action:
- L'action existe-t-elle dans le vecteur des actions ?
- oui:
- récupération de l'objet actionobj dans le vecteur
- return $actionobj->run($params, &$wiki)
- non: le fichier actions/$action.class.inc.php existe-t-il ?
- oui:
- chargement du fichier (ce qui définit la classe Action$action NB. je pense que les noms de classes sont insensibles à la casse en PhP)
- instanciation et stockage de l'instance dans $actionobj et dans le vecteur des actions sous la clef strtolower($action)
- continuer à partir du 1.a.2
- non: exécuter l'action à l'ancienne (inclusion etc.)
Implémentation
Voilà j'ai fait une première implémentation qui semble bien fonctionner et satisfaire les objectifs. Le code est assez long et pour pouvoir donner des exemples facilement je l'ai placé dans une autre page:
GererLesActionsSousFormeDObjetsSolution1?.
Discussions
Votre avis ?
voila un truc qui me plait bien, car pour le moment je suis en effet bloqué sur des trucs tous simple comme le header qui est chargé en premier par exemple, ce qui ne permet pas d'en faire autre chose que prévu. Et pour tous les avantages évoqués plus haut... --
Err404?
Je crois que la modularité est indispensable à la pérénité de wikini, et c'est un très bon axe de travail pour l'optimisation. --
Xf75013?
Tout ça me paraît très intéressant. En revanche j'ai un peu de mal à suivre ton raisonnement n'étant pas très versé dans la POO... pourais-tu nous fournir un exemple avec du code ?
Par exemple il pourrait s'agir d'une action permettant de définir le
<meta name="keywords" content="..............." /> de chaque page en plaçant l'action au début du texte :
{{metaKeywords content="objets, action, wikini"}}. Qu'en penses-tu ? --
CharlesNepote
- Le principe consiste simplement à créer un objet pour chaque action, au moment où on en a besoin. Ensuite cet objet est réutilisé pour les appels suivants (si l'action est appelée plusieurs fois).
- Ton exemple nécessiterait de faire interragir une action "metaKeywords" avec l'ActionHeader?, ce qui n'est évidemment pas exclu. Mais il serait possible de faire encore plus simple: l'affichage de l'entête pourrait se faire via une méthode spécial de l'objet décrivant l'action Header. Cette méthode n'étant pas la même que celle appelée lors d'une utilisation "normale" de l'action (l'utilisateur place l'action dans une page), il deviendrait impossible d'afficher l'entête en plein millieu de la page. Par contre, en appelant l'ActionHeader? avec d'autres paramètres, on pourrait en modifier le contenu. Per exemple:
- {{header settitle="Gérer les actions sous forme d'objets" setkeywords="objets, action, wikini" setdescription="Étude d'une solution orientée objet pour gérer les actions dans WikiNi"}}
- L'action pourrait éventuellement être appelée plusieurs fois pour éviter d'avoir une trop longue ligne. La seule condition pour que ça marche est de générer l'entête après le contenu de la page, c'est à dire NePasChargerHeaderEnPremier... (ce qui n'est pas un gros problème en soi, c'est d'ailleurs la seule chose que j'aie déjà implémentée même si ça ne m'est pas encore utile, cependant il faudrait modifier tous les Handlers qui utilisent l'ActionHeader?...)
- Je n'ai pas encore implémenté quoi que ce soit d'autre, d'une part pour avoir quelques avis, et d'autre part parce que je n'ai pas eu vraiment le temps... Je pense que le mieux est d'ajouter dans le noyau une méthode GetActionObject? qui retourne null si le fichier de description de la classe n'existe pas. Il suffirait donc d'appeler cette méthode depuis la méthode Action(), mais elle pourrait également être appelée à d'autres endroits pour faire des interractions directes avec les actions. -- LordFarquaad
Je me dis à présent que le choix de l'extension "
.class.inc.php" est un peu lourd et que "
.class.php" suffirait tout à fait en fait (quelle différence sémentique pourrait-il y avoir entre les deux ? Il me semble que "
.class.php" implique que c'est un fichier à inclure, ce qui rend le "
.inc" inutile...). Ceci serait d'autant mieux qu'apparemment certains IDEs (
Eclipse? par exemple) associent automatiquement cette extension à des fichiers contenant simplement une classe (avec un template prévu à cet effet). Qu'en pensez-vous ? --
LordFarquaad
LordFarquaadASuivre