1

Hello,

Attention, pavé !

Je relance un sujet que j'avais déjà abordé il y a quelques années (#vieuxsage#), mais pour lequel je n'ai toujours pas de solution. La question n'est pas "comment faire" puisqu'il y a 1000 et une façons de coder le petit exercice que je vais décrire, mais "comment bien faire" puisque je ne connais pas de solution qui me semble efficace. Vous avez peut-être pensé à des façons bien plus intelligentes que moi de structurer votre code, donc ça m'intéresse smile

Commençons par poser le vocabulaire. La grosse différence entre un site "non-ajax" et un site "ajax", c'est que dans le premier on a un certain nombre de pages entières et que chacune possède un point d'entrée : son URL. Dans un site "ajax", on a toujours des pages avec chacune son URL d'accès, mais il existe également d'autres points d'entrées pour des "fragments" de page, qui sont utilisés pour mettre à jour une partie du contenu de la page mais ne peuvent être affichés seuls. Jusqu'ici rien de neuf, mais maintenant que j'ai défini ce que j'appelle une page et un fragment on va pouvoir aborder l'exercice.

Prenons yAronet comme exemple, et imaginons que la page qui affiche la liste des topics utilise de l'ajax (en voilà une killer feature !). Cette page affiche initialement les 30 premiers topics d'un forum, avec des liens "précédent" et "suivant" qui permettent d'accéder aux 30 topics précédents ou suivants ceux qui sont actuellement affichés. J'ai donc une page "topics" (yn.com/topics.php?forum=X), et un fragment "topics_liste" (yn.com/topics_liste.php?forum=X&index=Y). La page affiche un document HTML habituel avec <head>, <body> & co, plus éventuellement plein d'autres informations comme mon login ou la liste des utilisateurs connectés, alors que le fragment affiche directement des balises HTML qui n'ont un sens que si elles sont insérées au bon endroit dans la page.

Puisque ma page affiche par défaut les 30 premiers topics, et que le fragment affiche "30 topics à partir d'un index donné", je suis très tenté d'utiliser le même code. Je vais donc avoir, quelque part, une fonction qui me récupère 30 topics à partir du N-ième, et qui va être appelée aussi bien dans ma page que mon fragment. Je conçois qu'on puisse vouloir désactiver le Javascript, et donc l'ajax, je voudrais en conséquence que ma page soit capable elle aussi d'afficher 30 topics à partir d'un index donné. Ça tombe bien, la fonction que je voulais réutiliser offre cette possibilité, autant ne pas s'en priver.

Je vais donc finalement avoir "yn.com/topics.php?forum=X&index=Y" qui affiche ma page entière avec 30 topics à partir du Y-ième, les utilisateurs connectés, et tout plein d'autres trucs, et "yn.com/topics_list.php?forum=X&index=Y" qui ne m'affiche qu'une liste de 30 topics à partir du Y-ième. Les bouts de code déclenchés par ces deux URLs vont faire appel à une même fonction "lister_topics" qui me donne une liste de 30 topics étant donné un forum et un index.

L'aspect de la liste des topics sera rigoureusement identique qu'elle soit affichée par ma page ou par mon fragment, je peux donc déjà écrire le "template" de ma liste (ou la "vue", si vous préférez) qui sera utilisé dans les deux cas. Mettons qu'il s'appelle "topics_liste.tpl.php", et qu'il ressemble à ça (faites abstraction de ce code hybride et imaginez un template réalisé à l'aide de votre framework de template favori) :

topics_liste.tpl.php: <a href="topics.php?forum=X&index=<?php echo $index_precedent; ?>" onclick="return ajax('topics_liste.php?forum=X&index=<?php echo $index_precedent; ?>');">Page précédente</a> <!-- TODO: la même avec $index_suivant, vous avez compris l'idée --> <ul> <?php foreach ($topics as $topic): ?> <li><a href="fixme"><?php echo $topic['name']; ?></a></li> <?php endforeach; ?> </ul>
Mes utilisateurs sont gentils et ne font pas d'injection dans les noms de topic.

Jusqu'ici tout va bien, mais il va falloir écrire le code qui va afficher ces deux templates, et c'est là que les choses se compliquent. Une chose est sûre, il va me falloir une fonction "afficher_topics ($forum_id, $index)" et une autre "afficher_topics_liste ($forum_id, $index)" qui vont être appelées quand quelqu'un accèdera à leurs URLs respectives. Pour le reste, plusieurs approches sont possibles, en voici quelques-unes et les défauts que je leur trouve :
Solution 1, la page effectue le rendu du fragment elle-même
topics.tpl.php: <html> <body> <h1>Affichage des topics du forum #<?php echo $forum_id; ?> :</h1> <div id="header"> <!-- TODO: Plein de trucs, affichage des utilisateurs connectés, d'un lien vers mon profil, d'un bouton "J'aime", etc. --> </div> <div id="topics"> <?php echo $fragment_topics ?> </div> </body> </html>

topics.php: <?php // Point d'entrée : yn.com/topics.php?forum=X&index=Y function afficher_topics ($forum_id, $index) { /* TODO: Vérifications d'usage, validité des arguments & co */ $utilisateurs = lister_utilisateurs (); $topics = lister_topics ($forum_id, $index); $fragment_topics = rendre ('topics_liste.tpl.php', array ( 'index_precedent' => max ($index - 30, 0), 'index_suivant' => $index + 30, 'topics' => $topics )); echo rendre ('topics.tpl.php', array ( 'utilisateurs' => $utilisateurs, 'fragment_topics' => $fragment_topics, 'forum_id' => $forum_id )); }?>

topics_liste.php: <?php // Point d'entrée : yn.com/topics_liste.php?forum=X&index=Y function afficher_topics_liste ($forum_id, $index) { /* TODO: Vérifications d'usage, validité des arguments & co */ $topics = lister_topics ($forum_id, $index); echo rendre ('topics_liste.tpl.php', array ( 'index_precedent' => max ($index - 30, 0), 'index_suivant' => $index + 30, 'topics' => $topics )); }?>
Pas terrible : j'ai dupliqué tout le code de rendu du fragment (l'appel à "rendre"). Durant tout le développement de ma future application il va falloir maintenir ces deux copies identiques, ce qui va devenir pénible quand j'aurai beaucoup de pages et de fragments. Les codes de rendu pouvant devenir facilement bien plus longs que ce cas d'école, je n'ai vraiment pas envie de les dupliquer.
Solution 2, on factorise le code de rendu dans une fonction
topics.tpl.php: <html> <body> <h1>Affichage des topics du forum #<?php echo $forum_id; ?> :</h1> <div id="header"> <!-- TODO: Plein de trucs, affichage des utilisateurs connectés, d'un lien vers mon profil, d'un bouton "J'aime", etc. --> </div> <div id="topics"> <?php echo $fragment_topics ?> </div> </body> </html>

topics.php: <?php include ('topics_liste.php'); // Point d'entrée : yn.com/topics.php?forum=X&index=Y function afficher_topics ($forum_id, $index) { /* TODO: Vérifications d'usage, validité des arguments & co */ $fragment_topics = rendre_topics_liste ($forum_id, $index); $utilisateurs = lister_utilisateurs (); echo rendre ('topics.tpl.php', array ( 'utilisateurs' => $utilisateurs, 'fragment_topics' => $fragment_topics, 'forum_id' => $forum_id )); }?>

topics_liste.php: <?php // Point d'entrée : yn.com/topics_liste.php?forum=X&index=Y function afficher_topics_liste ($forum_id, $index) { /* TODO: Vérifications d'usage, validité des arguments & co */ echo rendre_topics_liste ($forum_id, $index); } function rendre_topics_liste ($forum_id, $index) { $topics = lister_topics ($forum_id, $index); return rendre ('topics_liste.tpl.php', array ( 'index_precedent' => max ($index - 30, 0), 'index_suivant' => $index + 30, 'topics' => $topics )); }?>
C'est un peu mieux, je n'ai plus de code dupliqué. En revanche, je suis obligé de créer deux fonctions pour chaque fragment. Pire encore : si je veux ajouter un niveau d'abstraction et rester cohérent, la fonction "afficher_topics" ne devrait pas calculer le rendu elle-même. Il faudrait également la scinder en deux fonctions "afficher_topics" et "rendre_topics", la première appelant la seconde pour calculer le rendu. En gros, je viens de multiplier par deux le nombre de fonctions dans mon programme, et pour peu qu'il soit assez riche, ça représente un bon paquet de fonctions en plus dont j'aurais bien aimé me passer.
Solution 3, la page appelle l'affichage du fragment
topics.tpl.php: <html> <body> <div id="header"> <!-- TODO: Plein de trucs, affichage des utilisateurs connectés, d'un lien vers mon profil, d'un bouton "J'aime", etc. --> </div> <div id="topics"> <?php afficher_topics_liste ($forum_id, $index); ?> </div> </body> </html>

topics.php: <?php // Point d'entrée : yn.com/topics.php?forum=X&index=Y function afficher_topics ($forum_id, $index) { /* TODO: Vérifications d'usage, validité des arguments & co */ $utilisateurs = lister_utilisateurs (); echo rendre ('topics.tpl.php', array ( 'utilisateurs' => $utilisateurs, 'forum_id' => $forum_id, 'index' => $index )); }?>

topics_liste.php: <?php // Point d'entrée : yn.com/topics_liste.php?forum=X&index=Y function afficher_topics_liste ($forum_id, $index) { /* TODO: Vérifications d'usage, validité des arguments & co */ $topics = lister_topics ($forum_id, $index); echo rendre ('topics_liste.tpl.php', array ( 'index_precedent' => max ($index - 30, 0), 'index_suivant' => $index + 30, 'topics' => $topics )); }?>
Avec cette méthode, j'ai à nouveau une seule fonction par page ou fragment, c'est une bonne chose. J'ai aussi gagné quelque chose de très intéressant : le code de "afficher_topics" ne fait plus directement référence à "afficher_topics_liste", et ça c'est cool. J'ai repoussé cette liaison à la couche d'affichage, ce qui me permettra par exemple d'include de nouveaux fragments dans une page sans toucher à son code, c'est à dire du code qui n'a a priori aucun rapport direct avec les fragments en question. Bon point.
Par contre, je me tape les vérifications d'usage deux fois, c'est à dire que je dois m'assurer que $forum_id est un identifiant valide, qu'$index est cohérent, que l'utilisateur est bien connecté, etc. C'est d'autant plus dommage que je n'ai reçu qu'une seule requête, et que donc lors de mes deux vérifications $forum_id et $index n'ont pas changé de valeur. Mais à trop avoir voulu découpler mon code, j'ai perdu cette information, et je suis obligé de vérifier mes paramètres deux fois. Si ma page comportait 5 fragments au lieu d'un seul, ça pourrait devenir problématique, ne serait-ce qu'au niveau des performances.

Au final, la troisième approche me semble être la moins pire, mais elle garde un gros problème de code exécuté deux fois pour rien. Je pourrais limiter la casse avec un système de cache qui me permette de retenir qu'un paramètre est valide pour ne pas le vérifier à nouveau, mais ça va inévitablement allourdir mon code de rendu.

Et vous, vous faites/feriez comment ?
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)