PHP: echo vs printf vs strtr/str_(i)replace vs preg_replace_callback

PHP: echo vs printf vs strtr/str_(i)replace vs preg_replace_callback

2008-08-30 20:38:00 +0000

Introduction:

It is pretty frequent that we want to separate the text content of our websites from our website’s logic code. Sometimes that’s because we want to translate its text, other times just because of a matter of convenience.

Those texts would be, generally, mixed with dynamic context that we will have to replace.

Let’s suppose that we have a website with categories and posts of any kind and we want to show in the page’s title the path to a subcategory and the count of categories that it contains.

We could do something similar to this, using echo:

echo htmlspecialchars('Categoría: ' . implode(' > ', $path) . ' Subcategorías: ' . (int)$categories_count . ' Entradas: ' . (int)$entries_count);

It is easy to find out that even if it works, it is a bit hard to maintain; specially if the person that has to update it doesn’t know how to code. Furthermore if we would want to translate it to other languages, we would have to translate each part individually (with that echo).

Another option is to use printf function, that allows you to create a string of the format separating the actual data and allows us to separate it efficiently; also it makes much easier to localize text.

With printf we could do something like this:

define('TITLE_CATEGORY','Categoría: %s Subcategorías: %d Entradas: %d');  
printf(TITLE_CATEGORY, implode(' > ', $path), (int)$categories_count, (int)$entries_count));

That define, or a global variable, or a key inside an array, could be in a different file with all the localized texts together. That could also be in a database or in a text file that other person could edit. That would also allow us to format numbers or strings using printf capabilities.

But we lose the capability of change the order of the elements. Since PHP’s printf requires all the elements to be in the original order.

Using strtr, str_replace or str_ireplace, we ca achieve to have a formatting string that could allow to change the order of the elements easily and efficiently.

define('TITLE_CATEGORY', 'Categoría: {categories} Subcategorías: {categories_count} Entradas: {entries_count}');  
echo htmlspecialchars(strtr(TITLE_CATEGORY, array(  
'{categories}' => implode(' > ', $path),  
'{categories_rev}' => implode(' < ', array_reverse)($path)),  
'{categories_count}' => (int)$categories_count,  
'{entries_count}' => (int)$entries_count)  
)));  

You can see that I have added an additional key categories_rev, in the case the translator or a person updating the texts would decide to put the categories in the reverse order (for example for the SEO sake). Using preg_(i)replace, we could achieve the same result that using strtr if we use two arrays (one with keys and other with values) instead of an associative array.

To conclude, let’s add that using preg_replace and preg_replace_callback, we can achieve a much more powerful replacement, though a bit more complex and slow. Let’s illustrate it with an example, that allows us to apply a function to a parameter and to obtain those parameters:

function my_function($v) {  
    return strtr($v, 'aeios', '43105');  
}  
function my_replace_callback($k) {  
    $rl    = &$GLOBALS['my_replace_list'];  
    $funcs = &$GLOBALS['my_replace_list_funcs'];  
    $k = explode(':', $k[1]);  
    $key = array_shift($k);  
    $r = isset($rl[$key]) ? $rl[$key] : $key;  
    while (sizeof($k)) {  
        $func = array_shift($k);  
        if (in_array($func, $funcs)) $r = $func($r);  
    }  
    return $r;  
}  
function my_replace($t, $l, $f) {  
    $GLOBALS['my_replace_list'] = $l;  
    $GLOBALS['my_replace_list_funcs'] = $f;  
    return preg_replace_callback('/\\{([^\\}]+)\\}/', 'my_replace_callback', $t);  
}  
$path = array('objetos', 'inanimados', 'escolar', 'escritura');  
define('TITLE_CATEGORY', 'Categoría: {categories:my_function:strtoupper} Subcategorías: {categories_count} Entradas: {entries_count}');  
echo htmlspecialchars(my_replace(TITLE_CATEGORY, array(  
    'categories'       => implode(' > ', $path),  
    'categories_rev'   => implode(' < ', array_reverse($path)),  
    'categories_count' => 10,  
    'entries_count'    => 100,  
), array('strtoupper', 'trim', 'my_function')));

Conclusion:

  • echo is practical and the fastest option out there for simple things that doesn’t require formatting and that do not require translating or modification at all.
  • printf is practical for strings that require a format and simple translations
  • echo+strtr allows us to add a light formatting a bit more powerful than previous options
  • echo+preg_replace_callback allows us to add a much more costly formatting; it is the most flexible option out there and useful even for templates

Generally echo+strtr can do good enough results for text localization, offers a compact code with a good balance between computational cost and convenience and do not have any additional dependency on it.

Introducción:

En muchas ocasiones querremos separar el texto de nuestras páginas web de la programación propiamente dicho. En ocasiones será para poder localizar nuestra página (tenerla en diferentes idiomas), otras simplemente por comodidad.

Dichos textos, generalmente estarán mezclados con contenido dinámico que se tendrá que reemplazar.

Supongamos que tenemos una página con categorías y entradas de algún tipo y queremos mostrar en el título el camino a una subcategoría, y la cantidad de subcategorías y de elementos que tiene.

Podríamos hacer algo similar a esto usando echo:

echo htmlspecialchars('Categoría: ' . implode(' > ', $path) . ' Subcategorías: ' . (int)$categories_count . ' Entradas: ' . (int)$entries_count);  

No es difíl darse cuenta de que, aunque funciona, resulta complicado modificar y de mantener; especialmente si la persona que tiene que retocarlo no sabe programar. Además si quisiesemos traducirlo a otros idiomas tendríamos que traducir cada parte individualmente (con el echo).

Otra alternativa es usar la función printf, que permite crear una cadena de formato separada de los datos y nos permite una separación cómoda y eficiente; además de que facilita mucho todo lo referente a la localización de texto.

Con printf, podríamos hacer algo similar a esto:

define('TITLE_CATEGORY','Categoría: %s Subcategorías: %d Entradas: %d');  
printf(TITLE_CATEGORY, implode(' > ', $path), (int)$categories_count, (int)$entries_count));  

El define (o una posible variable global o clave en un array), puede estar en un fichero distinto con todos los textos a localizar o a modificar de fácil acceso. También puede estar en una base de datos o en un fichero de texto que pueda editar otra persona. Además nos permitiría dar formato a los números o a las cadenas.

Por otra parte perdemos la posibilidad de cambiar de orden los elementos. El printf requiere que los elementos estén en orden.

Con strtr o con str_replace o str_ireplace podemos conseguir una “cadena de formato” que permita cambiar el orden de los elementos de una forma ligera y eficiente:

define('TITLE_CATEGORY', 'Categoría: {categories} Subcategorías: {categories_count} Entradas: {entries_count}');  
echo htmlspecialchars(strtr(TITLE_CATEGORY, array(  
'{categories}' => implode(' > ', $path),  
'{categories_rev}' => implode(' < ', array_reverse)($path)),  
'{categories_count}' => (int)$categories_count,  
'{entries_count}' => (int)$entries_count)  
)));  

Se puede ver que he añadido una opción categories_rev, por si la persona que se encarga de traducir o de retocar los textos de la página determina que sería interesante colocar las categorías mas internas antes que las mas generales (por ejemplo para optimización para motores de búsqueda). Con preg_(i)replace, podríamos conseguir el mismo efecto que con strtr, si en vez de usar un array asociativa, usamos dos arrays, uno con las claves y otro con los valores.

Para finalizar decir que con preg_replace y preg_replace_callback, podemos conseguir un reemplazado mucho mas potente, aunque algo mas complejo y lento. Pondré un ejemplo en el que se nos permite aplicar una función a un parámetro y obtener los parámetros:

function my_function($v) {  
    return strtr($v, 'aeios', '43105');  
}  

function my_replace_callback($k) {  
    $rl    = &$GLOBALS['my_replace_list'];  
    $funcs = &$GLOBALS['my_replace_list_funcs'];  

    $k = explode(':', $k[1]);  
    $key = array_shift($k);  
    $r = isset($rl[$key]) ? $rl[$key] : $key;  

    while (sizeof($k)) {  
        $func = array_shift($k);  
        if (in_array($func, $funcs)) $r = $func($r);  
    }  

    return $r;  
}  

function my_replace($t, $l, $f) {  
    $GLOBALS['my_replace_list'] = $l;  
    $GLOBALS['my_replace_list_funcs'] = $f;  
    return preg_replace_callback('/\\{([^\\}]+)\\}/', 'my_replace_callback', $t);  
}  

$path = array('objetos', 'inanimados', 'escolar', 'escritura');  

define('TITLE_CATEGORY', 'Categoría: {categories:my_function:strtoupper} Subcategorías: {categories_count} Entradas: {entries_count}');  

echo htmlspecialchars(my_replace(TITLE_CATEGORY, array(  
    'categories'       => implode(' > ', $path),  
    'categories_rev'   => implode(' < ', array_reverse($path)),  
    'categories_count' => 10,  
    'entries_count'    => 100,  
), array('strtoupper', 'trim', 'my_function')));  

Conclusión:

  • echo es práctico y rápido para cosas sencillas que no requieran formato y que no se tengan que modificar o traducir.
  • printf es práctico para cadenas que requieran formato y una localización sencilla.
  • echo+strtr nos permite dar un formato ligero pero más potente que las otras dos opciones.
  • echo+preg_replace_callback nos permite dar un formato mas pesado, pero tremendamente flexible, útil incluso para templates.

Generalmente echo+strtr suele dar muy buenos resultados para localización de texto, y ofrece un código compacto de bastante rendimiento y sin ninguna dependencia adicional.