Páginas

2008/08/30

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

Introduction:

In a lot of situations we will want to separate the text of our websites from the programming code. In some situations it will be to be able to localize our website (being able to display it in different languages), in others simply by comfort.

Those texts, will generally being mixed with dynamic content that will need to be replaced.

For example. We have a site with categories in a tree and entries of some type, and we want to show the path to that subcategory, and the number of subcategories and elements it holds.

We could do that using a plain echo:

echo htmlspecialchars('Category: ' . htmlspecialchars(implode(' > ', $path)) . ' Number of subcategories: ' . (int)$categories_count . ' Number of entries: ' . (int)$entries_count);


It is easy to notice that, even when it works, it's hard to modify and to maintain; especially if the person who have to modify it doesn't know how to program. Futhermore if we want to translate it to other languages, we would have to translate each part individually (with the echo).

Other option is to use the printf function. It allows to you to have a string separated from the data, and allows us to create a separation easy and efficient; futhermore it ease all the localization stuff.

With printf, we could do something similar to this:

define('TITLE_CATEGORY','Category: %s Number of subcategories: %d Number of entries: %d');
printf(TITLE_CATEGORY, htmlspecialchar(implode(' > ', $path)), (int)$categories_count, (int)$entries_count));

Using a define (or maybe a global variable or a key in an array) can be in a different file with all the texts with translations with an easy access. Also it can be in a database or in a textfile to enable other person to modify.
Also it will allow us to format numbers and strings easily. For example: %.3f (for a decimal with three digits).

On ther other hand, we lose the possibility to change the element order. Rrintf requires the elements to be in order.

With strtr or str_replace or str_ireplace we will able to use a "format string" allowing to change the order of the elements in a lightweight and efficient way:

define('TITLE_CATEGORY', 'Category: {categories} Number of subcategories: {categories_count} Number of entries: {entries_count}');
echo htmlspecialchars(strtr(TITLE_CATEGORY, array(
'{categories}' => htmlspecialcharsimplode(' > ', $path)),
'{categories_rev}' => htmlspecialchars(implode(' < ', array_reverse)($path))),
'{categories_count}' => (int)$categories_count,
'{entries_count}' => (int)$entries_count)
)));

You can see that I have added a key categories_rev, to allow the person translating to put the categories in a reversed order if its appropiate in the language being translated or maybe if in terms of SEO it is better. Using preg_(i)replace, we could get the same effect that with strtr, but instead a associative array, we use two arrays, one with keys and other with values..

To finish I will notice that with preg_replace and preg_replace_callback, we can achieve a much powerful replacement, though somehow more complex and slow. For example:

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('inanimated', 'objects', 'scholar', 'writting');

define('TITLE_CATEGORY', 'Category: {categories:my_function:strtoupper} Number of subcategories: {categories_count} Number of entries: {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 for simple things that won't require format or translation.
  • printf is practical for strings requiring format or a simple translation.
  • echo+strtr will allow us to have a lightweight formating but powerfulier than the two first options.
  • echo+preg_replace_callback will allow us to have a more heavyweight format, but extremely flexible, useful even for templates.

Generally echo+strtr is a good option to translate texts, and offers a compact code with a very good performance and without any additional dependency.