PHPのstrtrは置換済みの文字を再置換しないstr_replaceとの違いと使い分け
strtrは、文字列中の文字や語を一括で置き換える関数で、str_replaceと違い一度置換した部分をもう一度置換しません。複数の置換を安全に同時に行いたいときに向いています。
str_replaceに配列を渡して複数置換をしていて、結果が思っていたものと違った、という経験はないでしょうか。私は「A→B」「B→C」の置換を並べたら、AがCになっていて首をひねったことがあります。原因はstr_replaceの置換が前から順番に走るからで、strtrはそこが違います。
str_replaceの複数置換はなぜ連鎖してしまうのか
str_replaceに配列を渡すと、検索語を先頭から1つずつ順に適用します。前の置換結果が、次の置換の対象になるわけです。
echo str_replace(['a', 'b'], ['b', 'c'], 'ab'); // cc
// 1回目: a→b で "bb"、2回目: b→c で "cc"
「aとbを入れ替えたい」といった処理だと、この連鎖が事故のもとになります。配列の順序に結果が依存するので、あとから要素を足したときに挙動が変わる、というのも地味に怖いところです。
strtrの配列指定はどう違うのか
strtrは2引数の形で「検索語 => 置換語」の連想配列を渡せます。置換済みの部分は再び走査されないので、連鎖しません。
echo strtr('ab', ['a' => 'b', 'b' => 'c']); // bc
$tpl = 'Hello {name}, you owe {amount}';
echo strtr($tpl, ['{name}' => '{amount}', '{amount}' => '100']);
// Hello {amount}, you owe 100
テンプレートのプレースホルダー置換で、ユーザー入力が値として入ってくるような場面では特に効きます。同じ置換をstr_replaceで書くと、先に入った値の中にある「{amount}」まで次の置換で書き換わってしまいます。値の中身をプレースホルダーと誤認しないという意味で、安全側に倒せる書き方です。
複数の検索語が重なるときはどちらが優先されるのか
strtrの配列指定は、同じ位置で複数の検索語が一致するとき、いちばん長い検索語を優先します。配列の並び順には左右されません。
echo strtr('aa', ['a' => '1', 'aa' => '2']); // 2
echo str_replace(['a', 'aa'], ['1', '2'], 'aa'); // 11
str_replaceでは先に書いた「a」が全部消費してしまい、「aa」の出番がなくなります。長さが違う語を混ぜて置換するなら、strtrのほうが並び順を気にせず書けて楽です。
3引数の形は何をするのか
strtrには、strtr($str, $from, $to) という3引数の形もあります。こちらは語ではなく1文字ずつの対応表で、fromのn文字目をtoのn文字目に変換します。
echo strtr('ab', 'ab', 'bc'); // bc
echo strtr('abc', 'abc', 'xy'); // xyc fromとtoの長さが違うと短いほうに合わせる
ここで1つ気をつけたいのが、この形はバイト単位で動くことです。マルチバイト文字をfrom、toに入れると、UTF-8のバイト列が分解されて文字化けします。たとえば strtr('ÀÉ', 'ÀÉ', 'AE') は意図した結果になりません。日本語や全角を扱うなら、3引数の形は避けて配列指定の形を使います。配列指定の形は文字列として照合するので、strtr('かな', ['か' => 'カ', 'な' => 'ナ']) は問題なく「カナ」になります。
空文字や空配列を渡すとどうなるのか
検索語に空文字を入れても置換は行われず、PHPはWarningを出して無視します。動的に作った配列に空文字が紛れ込む場合は、事前に取り除いておくのが無難です。一方、置換配列が空のときは警告もなく、元の文字列がそのまま返ります。
var_dump(strtr('abc', [])); // string(3) "abc"
まとめ
連鎖させたくない複数置換、長い語を優先したい置換、テンプレートの差し込みは、strtrの配列指定が向いています。単純に同じ語を1種類置き換えるだけなら、str_replaceで十分です。3引数の形は便利ですがバイト単位なので、日本語には使わない、と覚えておくくらいでちょうどいいと思います。
よくある質問
Q. strtrとstr_replaceはどちらが速いですか?
A. 用途次第で一概には言えません。速度よりも、置換が連鎖するかどうかという挙動の違いで選ぶほうが事故を防げます。
Q. 大文字小文字を区別せずにstrtrで置換できますか?
A. できません。strtrは常に大文字小文字を区別します。区別しない置換にはstr_ireplaceやpreg_replaceを使います。
Q. 日本語の置換にstrtrは使えますか?
A. 配列指定の形なら使えます。3引数の形はバイト単位で動くので、マルチバイト文字には向きません。