mb_strimwidthが意図した結果にならない場合

非常に基本的な話ですが、「mb_strimwidthが意図した結果にならない」という発言を何度か聞いたので、書いてみます。 まず、mb_strimwidthとは何か? リファレンスを読むと、 http://php.net/manual/ja/function.mb-strimwidth.php

mb_strimwidth — 指定した幅で文字列を丸める と書いてありますね。「幅」です。 「幅」とは何か?というと、mb_strwidthのところに書いてあります。
マルチバイト文字は、通常はシングルバイト文字の倍の幅となります。 つまり、シングルバイト文字の「幅」を1とした場合、マルチバイト文字の「幅」を2とする、ということです。 これはどうなるかというと、
$str_1 = 'ほげほげ';$str_2 = 'hogehoge';$width_1 = mb_strimwidth($str_1,0,6,'','utf-8');$width_2 = mb_strimwidth($str_2,0,6,'','utf-8');var_dump($width_1);var_dump($width_2); としたら、
string(9) "ほげほ"string(6) "hogeho" になるわけですね。 またこの結果内string(N)を見ても、バイト数は関係ないということになります。 おそらくこの関数は、固定幅のフォントでテキストの横幅を揃えることを意図したものなのでしょう。たとえばテキストで表組を描画する時など。 さて、では「意図しない結果」になる場合のパターンはどういったものでしょうか? パターン1:「1文字ズレる」
$str_1 = 'ほげほげ';$str_2 = 'hogehoge';$width_1 = mb_strimwidth($str_1,0,5,'','utf-8');$width_2 = mb_strimwidth($str_2,0,5,'','utf-8');var_dump($width_1); // var_dump($width_2); とすると、
string(6) "ほげ"string(5) "hogeh" となります。 width値にマルチバイト文字にとって切りが悪い数値が指定された場合、1字少なくなります。「丸める」という関数の意図から考えると当然そうあるべきだということになるでしょう。 パターン2:「幅が揃っているように見えない」 そもそも、出力結果テキストの「幅」が揃うためには、出力結果が「等幅フォント」でなければなりません。 しかし現在、HTML上のテキストは固定幅のフォントを使うことはあまりありません。 例えば、MSゴシックなどのフォントでは、全角の「(」は、直前の文字に対して非常に狭い横幅となります。 これはマルチバイト文字についてだけではなく、英数字についても、フォントセットによって、各文字の横幅や文字間の余白は各文字によって異なります。 この場合、テキストが長ければ長いほど、こうした文字幅の増減の影響を多く受けるため、テキスト全体の「幅」は揃わない、という結果になります。 パターン3:文字コード設定ミス (どんな言語でも同様ですが)文字コードの設定・指定はPHPにおいて非常に重要な要素です。特にmb系関数は文字コード設定によって挙動が変わるよう設計されています。 たとえば先ほどの例でいうと、
$str = 'ほげほげ';$width_1 = mb_strimwidth($str,0,6);$width_2 = mb_strimwidth($str,0,6,'','utf-8');var_dump($width_1); // var_dump($width_2); とすると、たとえば
string(6) "ほげ"string(9) "ほげほ" となります。(結果は環境によって異なります) 第5引数によって明示的に文字コードが指定されない場合、環境側での文字コード設定にフォールバックするため、結果が意図しないものになりがちです。 あくまで個人的な感覚ですが、解説本・文書ではこの点について触れているものが少ないような気がします。動けばいい、というならどうでもいいのかもしれませんが。 パターン4:「trimmarker値をつけると意図した結果にならない」 リファレンスのパラメータ定義に明記されてはいませんが、リファレンス内の「例」で、指定されているtrimmarker値の幅がwidth値に含まれる、という挙動が示されています。 ですので、
var_dump(mb_strimwidth('ほげほげほげほげ',0,4,'...','utf-8')); というように記述すると、
string(3) "..." という結果になります。 しかし一方で
trimmarker 丸めた後にその文字列の最後に追加される文字列。 と書いてあるので、例示・実際の挙動と定義が矛盾しています。 が、実はこの個所は、英語版では
trimmarker A string that is added to the end of string when string is truncated. となっています。 日本語版で「丸めた後に」とされている個所は、英語版では「丸められる時に」になっています。 版が違うのか誤訳なのか…いずれにせよ、2014/3現在での日本語リファレンスの記述は挙動に対して正しくありません。 trimmarker値を設定した場合、テキストの幅はwidth値からtrimmarker値を引いた数になる、というのが仕様のようです。 パターン5:「あれ?『設定文字数を超過したら丸める』という関数じゃないの?」 ではその想定で、 「全角半角問わず7字以上だったら7字に丸めて後ろに"..."をつける」 という記述を書いてみましょう。
var_dump(mb_strimwidth('ほげほげほげほげ',0,7,'...','utf-8')); 結果は
string(9) "ほげ..." になりますね。 "..."で幅3なので、残り幅4はマルチバイト文字2字分だということになります。 確かにこれは意図した結果になってはいないでしょう。 しかし繰り返しになってしまいますが、mb_strimwidthのwidthは「文字数」ではないので、挙動としては正しい挙動です。 上記想定を記述するとしたら、たとえば次のようなものになるはずです。
$str = 'ほげほげほげほげ';$length = mb_strlen($str,'utf-8');if($length>7){var_dump(mb_substr($str,0,4,'utf-8').'...');}else{var_dump($str);} 結果は次のようになります。
string(15) "ほげほげ..." ちなみにこの勘違いを持ったまま、twitterの「140字制限」にとりかかると多重でハマるのですがそれはまたの機会に…

最新記事

すべて表示

小ネタです。 SQLiteを使っていて "no such table" とエラーが出た場合、 DBファイル名の指定が空になっている、という凡ミスを起こしていないかを確認してみましょう。 ・・・ そういう凡ミスをしてしばし悩んだので… ファイル名の指定が空になっている場合、一時的なインメモリDBとして保存されます(※1)。 つまりDB接続を切断すると中身は消えます。 なので接続

最近、 Goで書かれたアプリケーションサーバが起動しない! ->原因: .env ファイルが欠けていた というドタバタがありました。 結局Goと関係ないですが、この時、 「あまりGoに慣れてないのでGoの問題かと…」「DockerまだよくわかってなくてDockerの問題かと…」 というような声があったのて、あえてGoで検証してみようと思ったわけです。 さて、Goでサーバサイドのシステムを作

弊社ではかなり前からGitLab(CE)を自社環境で運用しているのですが、ふと気付くと、バージョンがだいぶ先に行ってしまっていました。 とくに最近のバージョンでは Auto DevOps なども使えるようになっていたりするので、さすがにそろそろキャッチアップしたいと考えたわけです。 現行の環境は次の通りです: GitLab 9.1.2 sameersbn/gitlab 使用 MySQL 5.6