Blog // Exirel.me

Tu ne routeras point comme un cochon

Par Florian Strzelecki - 19:57 - 11.04.2012

Tags : Framework, Web, Documentation, Bonne pratique, PHP, loldev, Technique

Ces derniers jours, j'ai un problème avec les URLs. Plus spécifiquement, avec certaines URLs, qui sont gérées par certains mécanismes de "routage d'url" (et de réécriture d'url).

Il y a les bons routages et les mauvais routages.

Les bons routages d'URLs considèrent un format type (à base d'expressions régulières par exemple), et permettent d'associer une URL concrète à ce format, et d'en tirer une représentation à usage interne pour l'application. Les mauvais routages d'URLs considèrent un format type (à base d'expressions régulières par exemple), et permettent d'associer une URL concrète à ce format, et d'en tirer une représentation à usage interne pour l'application. Sauf que c'est un mauvais routages d'URL.

Trêve de blagues (et faisons plutôt la paix) (...) (ok j'arrête) voici le vrai problème que j'ai en ce moment : lorsque / et /home/default produisent exactement le même résultat, c'est à dire, pointent finalement sur la même "ressource". Autant c'est bien gentil de vouloir de jolies urls, et d'avoir des mécanismes simples pour gérer automatiquement un tas de cas, mais ce n'est quand même pas très RESTFull au final (je hais la duplication de contenu).

Car il s'agit bien d'un problème de "comment", et pas de "quoi" : réécrire des URLs à usage interne, et adapter le routage en conséquence, c'est tout à fait normal - ce n'est pas sale. Mais autant le faire bien, s'il vous plait. Ce problème n'est malheureusement que rarement traité, même si lors de mes recherches, j'ai été agréable surpris par les documentations de Symfony 2 et CakePHP.

Faisons un petit tour d'horizon de ce que nous proposent certains frameworks "à la mode" du monde PHP, puisque c'est majoritairement chez eux que j'ai rencontré ce problème, que chaque développeur devrait connaître et savoir gérer. Je veux dire, en commençant par reconnaître le problème quand ils l'ont devant leurs yeux.

Zend Framework

Allez, on commence avec du lourd, du sale qui tache : ce bon vieux Zend Framework. Après toutes ces années, je ne sais pas pourquoi je dis encore "ce bon", parce que je le trouve tout sauf bon, et même si ce n'est pas le sujet, encore une fois, ce framework montre des faiblesses de conception.

Dans sa documentation concernant le système de routage d'URL, vous pouvez tomber sur Zend Framework: Default Routes, section qui vous explique ceci :

Zend_Controller_Router_Rewrite comes preconfigured with a default route, which will match URIs in the shape of controller/action.

Ah, donc non seulement c'est le routeur par défaut, mais en plus il a pile le comportement qu'on ne veut pas avoir. Heureusement qu'à la fin de cette section il est ajouté qu'on peut supprimer cette route par défaut... bien que, par expérience, les résultats sont plutôt hasardeux (du genre vraiment, et pénible avec ça).

Cela dit, ce n'est pas encore terminé, sur la partie Dispatcher vous pouvez lire ceci :

If any of the module, controller, or action are not found, it will use default values for them.

Et pour revenir sur mes expériences : oui, ça peut devenir un problème, puisque le mécanisme qui utilise les routes d'URLs a déjà sa façon de voir les choses, et ce n'est pas toujours à votre avantage. Rien que d'y penser... non, je n'ai pas envie d'y penser.

CakePHP : presque !

Je ne connais pas bien CakePHP, et de ce que j'en ai lu, ce n'est pas trop mal - mais bon, mon avis ne vaut pas grand chose, testez plutôt vous-même. Que nous dit la documentation de CakePHP : Default Routing ?

You can access an action directly via the URL by putting its name in the request.

Brrr... ok, la même chose que pour Zend-Framework (mais avec une documentation plus sympa cela dit), ce qui n'est pas vraiment une bonne chose. D'autant que la suite n'est pas mieux, puisqu'on retrouve même des conseils sur comment faire de mauvaises choses automatiquement :

The keys of the array should be named after the route elements in the URL, or the default elements: :controller, :action, and :plugin. The values in the array are the default values for those keys.

Erm... je reviens, je vais boire une litre ou deux de ice-tea, pour me calmer. Ah moins que... CakePHP : Disabling the default routes : en voilà deux paragraphes intéressants !

Dommage qu'ils ne prennent pas beaucoup de place, mais ils ont le mérite d'être là. Je retiens plus particulièrement ceci, qu'il faudrait peut-être mettre en gras, en gros, avec des néons et des balises <blink></blink> tout autour (non, je déconne, les néons c'est en trop) :

If you have fully customized all your routes, and want to avoid any possible duplicate content penalties from search engines [...]

Bon, d'accord, la raison mise en avant, c'est pour faire plaisir aux moteurs de recherches : en attendant, c'est déjà une mise en garde sur les dangers d'un routage un peu trop permissif et automatique. Un bon point, au moins (et puis la doc a vraiment une jolie tête).

Symfony : le 1, et puis le 2 ensuite

J'ai connu une version de la branche 1.x de Symfony, et si le framework ne m'a pas marqué plus que ça, je me souviens très bien de son système de routes, qui est relativement facile à configurer (notez que je ne parle jamais de performance ici). Cette fois je peux nuancer plus facilement mon discours.

Tout d'abord, il est parfaitement possible de se passer d'une route par défaut avec Symfony 1.x, et je le conseille même vivement. Le soucis, c'est que la documentation section 9 ne précise pas forcément très bien cet aspect là. Je reprends cet exemple de code fourni :

# generic rules
# please, remove them by adding more specific rules
default_index:
  url:   /:module
  param: { action: index }
default:
   url:   /:module/:action/*

Alors, oui, il y a un commentaire, mais c'est à peu près la seule remarque sur le sujet, et j'ai vu bien des développeurs l'ignorer complètement, l'oubliant, et reléguant cette ligne au fin fond des poubelles de l'histoire de leur framework favori.

D'ailleurs, un exemple de code un peu plus loin ne reprend déjà plus le commentaire, c'est dire... bref, là encore, Symfony rejoint la liste des frameworks qui permettent des choses bien crades, et qui en plus ne documentent pas bien la chose. Pas cool.

Symfony 2.x : de bonnes inspirations ?

N'ayant pas travaillé avec Symfony 2, j'ai du faire un peu plus de recherches, et j'ai été agréablement surpris par une documentation bien plus claire, et plus encore, par le système de routage.

Ce derniers ne propose pas (de manière documentée explicitement en tout cas), un mécanisme "générique" pour des urls qui peuvent correspondre automatiquement à une représentation interne (du genre :controller/:action comme dans la version 1.x). En cherchant un peu, j'ai trouvé que c'était tout à fait possible de mal faire les choses quand même, mais cela me semble moins grave, du fait d'une information qui n'est pas mise en avant du tout (là encore, contrairement à la version précédente).

Bref, bon point pour Symfony 2.x, qui propose un mécanisme de routage d'URLs relativement intelligent. Le point bonus : il propose même un système d’annotations, comme les décorateurs en Python, pour gérer ses URLs... un peu à la manière de Pyramid (un framework web minimaliste en python).

TL;DR: Tu ne routeras point comme un cochon

Vous l'aurez compris aux travers de ces exemples, je ne critique pas le principe de routage d'URLs en lui-même, mais seulement son implémentation, sa documentation, et les conseils fournis aux développeurs. Il y a dans la conception de ces outils une mauvaise compréhension des contraintes posées par les URLs et les bonnes pratiques. Les solutions apportées, bien que facile d'utilisation (et encore...), ne me semblent pas bien répondre aux problématiques posées, ou alors, seulement en partie, alors que toutes les parties sont importantes.

Je compare ces problèmes de conceptions à d'autres implémentations : celles de Django et de Pyramid, deux frameworks web en python. Ni l'un, ni l'autre, ne permettent simplement d'instaurer un tel mécanisme, mais proposent à la place une grande souplesse et une grande réutilisabilité.

Comme quoi, c'est un problème de conception, pas de fonctionnalité.

Sinon, une dernière blague pour la route ? Essayons de ne pas nous fâcher en si bon chemin !