Compter les caractères et les octets en PHP

Le sujet de cet article peut sembler simpliste, mais il ne l’est pas tant que ça, parce qu’il peut engendrer des bugs assez délicats à débusquer.

Le problème

Dans n’importe quel langage de programmation, on se retrouve souvent à vouloir connaître la longueur des données stockées dans une variable. Dans le cas d’une chaîne de caractères ou de données binaires, les choses peuvent parfois être plus délicates qu’on pourrait le croire au premier regard.

La base, c’est la fonction strlen() du C, qui retourne la longueur d’une chaîne de caractère… en comptant qu’un caractère est égal à un octet. Et ça fonctionnait très bien tant qu’on était sur des encodages mono-octet : ASCII, latin1 (ISO-8859-1), latin9 (ISO-8859-15), etc.

Sauf que de nos jours, nous utilisons tous − fort heureusement − l’Unicode. Et si vous êtes à peu près sain d’esprit, vos fichiers sont encodés en UTF-8, qui est un encodage particulièrement intelligent (compatible avec l’ASCII pour les caractères latins de base). Il faut donc avoir en tête que les caractères ainsi encodés peuvent prendre de un à quatre octets.
Donc le nombre de caractères et le nombre d’octets ne sont pas du tout la même chose.

En PHP

Si on revient au PHP, on peut voir assez souvent du code qui a besoin de connaître la longueur d’une chaîne de caractères, et qui fait quelque chose de ce genre :

$taille = strlen($chaine);

Sauf que le problème, évidemment, c’est que la fonction strlen() de PHP est comme celle du C : elle ne retourne pas le nombre de caractères, mais le nombre d’octets de la chaîne.

$chaine = 'héhé';
$taille = strlen($chaine); // = 6

La solution, c’est d’utiliser la fonction mb_strlen(). Comme son nom l’indique, elle gère les caractères multioctets (“mb” pour “multibyte”). On l’utilisera en spécifiant l’encodage utilisé en deuxième paramètre :

$chaine = 'héhé';
$taille = mb_strlen($chaine, 'UTF-8'); // = 4

Très bien, mais dans les cas où on veut connaître le nombre d’octets, et non pas le nombre de caractères (par exemple si on a des données binaires, ou bien si on veut s’assurer qu’on va pouvoir stocker la chaîne dans un espace donné), comment faire ?

On pourrait se dire que la fonction strlen() est justement là pour ça. Et c’est vrai que ça fonctionne très bien d’un point de vue purement technique.
Par contre, pour la relecture de code, c’est une horreur. À chaque fois que je vois un strlen(), je me demande si le développeur voulait réellement compter le nombre d’octets, ou s’il ne connaît simplement pas la subtilité et qu’il est allé au plus simple.

Donc pour compter le nombre d’octets, on va utiliser mb_strlen() en lui disant d’utiliser le code ASCII, pour qu’il compte un caractère par octet :

$chaine = 'héhé';
$taille = mb_strlen($chaine, 'ASCII'); // = 6

Donc comme ça les choses sont claires : dans tous les cas il faut utiliser mb_strlen(), et l’encodage passé en paramètre permet de connaître précisément l’intention du développeur :

  • UTF-8 pour compter les caractères
  • ASCII pour compter les octets

Et ailleurs ?

Pour info, le PHP n’est pas le seul langage qui demande un peu de gymnastique.

En Python, pour obtenir le nombre de caractères dans une chaîne, il faut faire :

taille = len(chaine)

Pour obtenir le nombre d’octets, il faut réencoder la chaîne, ce qui fait des traitements supplémentaires :

taille = len(chaine.encode('utf-8'))

En Javascript, pour obtenir le nombre de caractères :

taille = chaine.length;

Pour obtenir le nombre d’octets, il faut là encore réencoder la chaîne :

taille = (new TextEncoder().encode(chaine)).length;

Le Ruby offre une gestion native par la classe String, tant que l’encodage par défaut est défini correctement.
Pour récupérer le nombre de caractères :

taille = chaine.length

Pour avoir le nombre d’octets :

taille = chaine.bytesize

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Notifiez-moi des commentaires à venir via email. Vous pouvez aussi vous abonner sans commenter.