Transformer une chaîne de caractères en URL

Voir en ligne : Test de la fonction

Il y a quatre ans, j’avais écrit un article sur Webmaster-Hub, pour y présenter une fonction de création d’urls à partir d’une chaîne de caractères. Le principal souci était le remplacement des accents par des caractères non accentués.

Dans le principe, je voulais une fonction qui fasse les actions suivantes :

  • Transformation de tous les caractères accentués en leur version non accentuée
  • Suppression de tous les caractères non alphanumériques (Ponctuation, etc)
  • Séparation de tous les mots par un seul tiret

Il existe bien une fonction url_encode() en PHP, mais elle sert à passer une chaîne de caractères en paramètres, de manière à pouvoir la décoder par la suite. Ainsi, tous les caractères (Ponctuation comprise) sont encodés, et le résultat n’est pas des plus lisibles.

J’étais également parti des constats suivants :

  • La majorité des fonctions qu’on trouve sur Internet proposent un remplacement caractère par caractère [1], ce que je trouve bancal, parce qu’il n’automatise rien : il est nécessaire de lister tous les caractères accentués, ainsi que leur version non accentuée.
  • Un caractère accentué, encodé en HTML, se trouve toujours de la forme suivante : « & + lettre + code_accent + ; »

Ma première fonction faisait donc un htmlentities sur le texte, puis une expression régulière supprimait les parties non nécessaires. Une seconde fonction faisait de même avec les ligatures : « & + lettres + lig + ; », pour l’e dans l’o (œ). Toutefois, il m’a fallu développer une exception pour l’eszett allemand (ß), qui se code « & + sz + lig ; » et se traduit souvent par « ss » (Straße devient Strasse, etc). Et c’était sans compter sur d’autres caractères éventuels qui seraient passés à la trappe.

Toujours sur le forum de Webmaster-Hub, SStephane m’a proposé une fonction bien plus compacte et efficace, que je me propose de vous faire découvrir ici.

function string_to_url($str) {
        $str    =       preg_replace('#[^\\p{L}\d]+#u', '-', $str);
        $str    =       trim($str, '-');
        $str    =       iconv(mb_detect_encoding($str), 'us-ascii//TRANSLIT', $str);
        $str    =       strtolower($str);
        $str    =       preg_replace('#[^-\w]+#', '', $str);
        return  $str;
}

En voici les détails :

preg_replace('#[^\\p{L}\d]+#u', '-', $str);

Cette première expression régulière récupère tout ce qui n’est pas (^) un caractère unicode (\\p{L}) ou un chiffre (\d), et le transforme en tiret. L’option « u » force le traitement de la chaîne en UTF-8.

trim($str, '-');

Cette ligne toute simple permet de supprimer tous les tirets au début et à la fin de la chaîne.

iconv(mb_detect_encoding($str), 'us-ascii//TRANSLIT', $str);

Cette ligne est la plus importante de la fonction. La fonction mb_detect_encoding() permet de détecter l’encodage du texte actuel. La fonction iconv() permet quant à elle de passer une chaîne de caractères d’un encodage à un autre (Ici, de l’encodage trouvé à de l’ASCII, ne possédant aucune lettre accentuée). L’ajout de la chaîne « //TRANSLIT » permet à la fonction de remplacer les caractères non représentables par son équivalent le plus proche dans le jeu de caractères demandé. Cela nous permet d’afficher la version non accentuée des lettres au lieu de générer une erreur.

strtolower($str);

Cette ligne permet simplement de mettre toute la chaîne en minuscules.

preg_replace('#[^-\w]+#', '', $str);

Quant à cette dernière ligne, elle permet de supprimer tout caractère qui ne soit (^) ni un tiret (-), ni une lettre ou un chiffre (\w). Elle permet de ne pas générer d’erreur avec des caractères qui seraient passés au travers des fonctions précédentes.

Voir en ligne : Test de la fonction

Notes

[1Comme cette fonction, par exemple

Post-scriptum :

J’ai rencontré des soucis sur un serveur, où malgré le //TRANSLIT, la fonction iconv() ne tenait pas compte des caractères accentués. Ça a été corrigé en utilisant la fonction setlocale() en début de fichier, avec les paramètres suivants :

setlocale(LC_CTYPE, 'fr_FR');

J’avais fait plusieurs essais, notamment avec la constante LC_ALL, qui n’avait pas fonctionné. L’utilisation de LC_CTYPE au lieu de LC_ALL permet également d’éviter de générer des soucis de gestion de nombres relatifs, comme mentionné sur ce commentaire.

J’ai récemment eu besoin de savoir quelles locales étaient installées sur mon serveur : la commande locale -a m’a permis de les lister (Et j’ai découvert que je cherchais la locale fr_FR.utf8).

Laissez votre commentaire