PHP trabaja de fábrica con cadenas de bytes, no con caracteres Unicode. Eso significa que strlen("árbol") devuelve 6, no 5, porque la á ocupa dos bytes en UTF-8. La extensión mb_string resuelve ese problema: sus funciones entienden la codificación real del texto y cuentan caracteres, no bytes.
Activar mb_string
En la mayoría de servidores modernos ya viene habilitada. Puedes comprobarlo con:
php -m | grep mbstring
Si no aparece, añade extension=mbstring en tu php.ini y reinicia el servidor web. En Ubuntu/Debian: sudo apt install php-mbstring && sudo phpenmod mbstring.
mb_strlen y mb_substr
Las funciones más usadas del módulo. Siempre pasa la codificación explícita para evitar sorpresas si mbstring.internal_encoding difiere entre entornos:
<?php
$texto = "árbol";
echo strlen($texto); // 6 cuenta bytes
echo mb_strlen($texto, 'UTF-8'); // 5 cuenta caracteres
$emoji = "Hola ?";
echo mb_strlen($emoji, 'UTF-8'); // 6
// Extraer los primeros 3 caracteres
echo mb_substr($texto, 0, 3, 'UTF-8'); // árb
// Árabe: cada letra es multibyte
$arabe = "?????";
echo mb_strlen($arabe, 'UTF-8'); // 5
echo mb_substr($arabe, 1, 3, 'UTF-8'); // ???
mb_strpos y mb_strrpos
Búsqueda de subcadenas respetando la codificación:
<?php
$frase = "El niño juega en el patio";
$pos = mb_strpos($frase, "niño", 0, 'UTF-8');
echo $pos; // 3
// Búsqueda insensible a mayúsculas
$pos = mb_stripos("Árbol Grande", "árbol", 0, 'UTF-8');
echo $pos; // 0
// Última ocurrencia
$cadena = "test-abc-test";
echo mb_strrpos($cadena, "test", 0, 'UTF-8'); // 9
mb_strtolower y mb_strtoupper
Las funciones nativas strtolower y strtoupper no conocen caracteres como Á, Ñ o Ü. Las versiones mb sí:
<?php
echo strtolower("ÁRBOL"); // ÁRbol falla con la Á
echo mb_strtolower("ÁRBOL", 'UTF-8'); // árbol correcto
echo mb_strtoupper("über cool", 'UTF-8'); // ÜBER COOL
// Chino: mb_strtolower no tiene efecto (ya no hay mayúsculas)
// pero mb_strlen funciona correctamente
$chino = "????";
echo mb_strlen($chino, 'UTF-8'); // 4
mb_str_split
Disponible desde PHP 7.4. Divide una cadena en un array de caracteres (o fragmentos) respetando la codificación:
<?php
$texto = "café";
print_r(str_split($texto));
// Array ( [0] => c [1] => a [2] => f [3] => Ã [4] => © )
// str_split parte bytes, no caracteres
print_r(mb_str_split($texto, 1, 'UTF-8'));
// Array ( [0] => c [1] => a [2] => f [3] => é )
// Dividir en grupos de 2 caracteres
print_r(mb_str_split("árbol", 2, 'UTF-8'));
// Array ( [0] => ár [1] => bo [2] => l )
mb_convert_encoding
Cuando recibes texto de fuentes externas (ficheros legacy, APIs antiguas, emails) puede venir en ISO-8859-1, Windows-1252 o similares. mb_convert_encoding convierte entre cualquier par de codificaciones que soporte mb_string:
<?php
// Leer un CSV en ISO-8859-1 y pasarlo a UTF-8
$contenido = file_get_contents('clientes_old.csv');
$utf8 = mb_convert_encoding($contenido, 'UTF-8', 'ISO-8859-1');
// Detectar la codificación automáticamente
$detectada = mb_detect_encoding($contenido, ['UTF-8', 'ISO-8859-1', 'Windows-1252'], true);
echo $detectada; // ISO-8859-1
// Convertir desde detección automática
$utf8 = mb_convert_encoding($contenido, 'UTF-8', $detectada ?: 'ISO-8859-1');
mb_substr_count
Cuenta cuántas veces aparece una subcadena, respetando codificación:
<?php
$texto = "niño, niña y niñez";
echo substr_count($texto, "niñ"); // puede fallar
echo mb_substr_count($texto, "niñ", 'UTF-8'); // 3
Configuración global en php.ini
Puedes establecer una codificación interna por defecto para no tener que pasarla en cada llamada:
; php.ini
mbstring.internal_encoding = UTF-8
mbstring.http_output = UTF-8
mbstring.http_input = UTF-8
O bien al inicio de tu aplicación:
<?php
mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');
// A partir de aquí puedes omitir el parámetro de codificación:
echo mb_strlen("árbol"); // 5
Errores frecuentes
- Mezclar strlen con mb_strlen: usar
strlenpara validar longitudes de contraseñas o usernames en UTF-8 produce límites incorrectos. Usa siempremb_strlen. - Olvidar la codificación: si
mbstring.internal_encodingno está a UTF-8 y omites el parámetro, los resultados varían entre servidores. - str_split en textos multibyte: parte bytes, no caracteres. Sustituye siempre por
mb_str_split. - preg_ con textos Unicode: añade el modificador
/upara que las expresiones regulares traten la cadena como UTF-8:preg_match('/p{L}+/u', $texto).
Ejemplo completo: truncar un título sin cortar palabras
<?php
function truncar(string $texto, int $max, string $enc = 'UTF-8'): string {
if (mb_strlen($texto, $enc) <= $max) {
return $texto;
}
$corte = mb_strrpos(mb_substr($texto, 0, $max, $enc), ' ', 0, $enc);
if ($corte === false) {
$corte = $max;
}
return mb_substr($texto, 0, $corte, $enc) . '
';
}
echo truncar("El árbol genealógico de la familia Martínez", 25);
// El árbol genealógico de
Con mb_string tienes acceso a más de 100 funciones multibyte. Las cubiertas aquí son las que aparecen en el 90% de los proyectos reales. A partir de ellas el resto resulta intuitivo: son variantes con el prefijo mb_ de sus equivalentes estándar.
