PHP – Is this an efficient way to make these changes?

I care a lot about capitalization (probably too much). So I wrote a function that fixes the capitalization everywhere on my site. I basically want “title case” but with some exceptions… words I don’t like to see capitalized and acronyms.

function my_capitals($string)
    $uc = ucwords($string);
    $tokens = explode(' ',$uc);
    foreach ($tokens as $key=>$val)
        if ($val == 'Ipa') $tokens[$key] = 'IPA';
        else if ($val == 'Ipas') $tokens[$key] = 'IPAs';
        else if ($val == 'Apa') $tokens[$key] = 'APA';
        else if ($val == 'Apas') $tokens[$key] = 'APAs';
        else if ($val == 'A') $tokens[$key] = 'a';
        else if ($val == 'And') $tokens[$key] = 'and';
        else if ($val == 'The') $tokens[$key] = 'the';
        else if ($val == 'In') $tokens[$key] = 'in';
        else if ($val == 'Or') $tokens[$key] = 'or';
        else if ($val == 'Of') $tokens[$key] = 'of';
        else if ($val == 'To') $tokens[$key] = 'to';
        else if ($val == 'On') $tokens[$key] = 'on';
        else if ($val == 'At') $tokens[$key] = 'at';
        else $tokens[$key] = $val;
    $final = implode(' ',$tokens);
    return $final;

Imagine there might be another 10-15 options and that it may be run 3-5 times per page on relatively short strings (one-line descriptions and titles).

My question is this: Is this an efficient way to accomplish this sort of translation? Or should I be figuring out a more efficient way to do it? Is there another alternative I don’t know of, as opposed to just switch which probably has similar performance?

I would do it this way:

   function my_capitals($string)
        $uc = ucwords($string);
        $tokens = explode(' ',$uc);
        $excepsions = ['Ipa'=>'IPA','Ipas'=>'IPAs'];
        foreach ($tokens as $key=>$val)
                $tokens[$key] = $excepsions[$val];
        $final = implode(' ',$tokens);
        return $final;

First of all, you have to consider what efficient means to you. Are you looking for

  • the shortest execution time
  • the smallest system impact (CPU, RAM, I/O …)
  • the cleanest code (efficient coding)
  • the shortest code

Second, given your details that …

  1. there are around 30 search terms
  2. strings are short
  3. and code is fired up to 5 times

… unless you are using a toaster for your script, neither execution time nor system impact will give you by any means any kind of headache.

So it’s up to a clean code actually. Therefore, you should get known to the difference of == and ===.
Next, you already have a string which is searchable for several string specific functions : $uc.

So what about str_replace? It accepts arrays as input.

function my_capitals($string)
    $uc = " ".ucwords($string)." ";
    $search = [' Ipa ', ' Ipas ', ' A ', ' Bändy '];
    $replacements = [' IPA ', ' IPAs ', ' a ', ' Cändy '];
    return ucfirst(trim(str_replace($search, $replacements, $uc)));

You can even shorten that to 1 line:

function my_capitals($string)
    return ucfirst(trim(str_replace([' Ipa ', ' Ipas ', ' A ', ' Bändy '], [' IPA ', ' IPAs ', ' a ', ' Cändy '], " ".ucwords($string)." ")));

Just make sure $search and $replacements do contain an equal amount of elements and both have leading and trailing whitespaces.

Allocating your arrays just once will improve speed on consecutive calls.

function my_capitals1a($string, $searcher, $replacement)
    return ucfirst(trim(str_replace($searcher,$replacement, " ".ucwords($string)." ")));

Demo and speed comparison:

Edit: word safe replacement
Edit2: speed comparison
Edit3: improved with hints from @Pogrock

A slight variation of the OP’s code. Upper case all words in the input title, and then swap words like And for and. Finally initialise the beginning of the title.

You could canonicalise your title first with string to lower, but this could break names such as O’Hagan.

There are many other edge cases not covered, and you may be better to trade in speed for a full featured library.

$replacements = [
        'Ipas' => 'IPAs',
        'Apa'  => 'APA',
        'Apas' => 'APAs',
        'A'    => 'a',
        'And'  => 'and',
        'The'  => 'the',
        'In'   => 'in',
        'Or'   => 'or',
        'Of'   => 'of',
        'To'   => 'to',
        'On'   => 'on',
        'At'   => 'at',

$test_titles = [
    'a tale of two cities'    => 'A Tale of Two Cities', 
    'the secret history'      => 'The Secret History',
    'lord of the flies'       => 'Lord of the Flies',
    'The woman in white'      => 'The Woman in White',
    'of mice and men'         => 'Of Mice and Men',
    'the andy warhol diaries' => 'The Andy Warhol Diaries'

foreach($test_titles as $input => $title_cased) {
    $words = [];
    foreach(explode(' ', ucwords($input)) as $word) {
        $words[] = isset($replacements[$word]) ? $replacements[$word] : $word;
    $transformed = ucfirst(implode(' ', $words));
    assert($transformed === $title_cased);