【PHP】mb_strlenやmb_strwidthは使えない!?

PHPの覚書です。

strlenとmb_strlenの違い

  • strlen: バイト数を数えます。
  • mb_strlen: 文字数を数えます。
  • mb_strlen(“こんにちは”) は 5 を返します。
  • mb_strlen(“Hello”) も 5 を返します。
$text = "あいうABC123";
echo strlen($text);    // 出力: 15 (3*3 + 3 + 3 = 15バイト)
echo mb_strlen($text); // 出力: 9 (文字数を正確に数える)

UTF-8での文字のバイト数

  • 英語(ASCII文字): 1バイト
  • 日本語(ひらがな、カタカナ、漢字): 3バイト
  • 一部の特殊文字や絵文字: 4バイト

mb_strwidthについて

  • 文字の表示幅を考慮します。
  • 半角: 1として数える
  • 全角: 2として数える
$text = "あいうABC123";
echo mb_strwidth($text); // 出力: 12 (3*2 + 3 + 3 = 12)
スポンサーリンク

mb_strwidthは使えない?

パーフェクトじゃないけど、普通に使っているようですね。

  • mb_strwidthは全角文字を2、半角文字を1としてカウントしますが、これは必ずしも実際の表示幅と一致しません。
  • 特に問題になるのは以下のような文字です:
    • 結合文字(例:アクセント記号)
    • 絵文字
    • サロゲートペア(Unicode の拡張領域の文字)

mb_strwidthが間違える主なケース:

  • 絵文字: 多くの絵文字は2とカウントされますが、実際の表示幅はより広いことがあります。
  • 結合文字: アクセント記号などが別にカウントされ、実際の表示より広く見積もられることがあります。
  • 一部の特殊な Unicode 文字: 表示幅が正確に判断できない場合があります。

mb_strwidthは多くの一般的なケースで十分に機能しますが、完璧ではありません。以下のように考えます:

a. 実用性:

  • 通常の日本語テキストや英数字を扱う場合、mb_strwidthは十分に実用的です。
  • 多くのWebアプリケーションやCMSでの使用には適しています。

b. 限界:

  • 絵文字を多用するSNSアプリケーションや、多言語対応が必要な国際的なアプリケーションでは注意が必要です。
  • 正確な表示幅が極めて重要な場合(例:厳密なレイアウトが要求される印刷物)には不適切かもしれません。

c. 代替案:

  • より正確な幅計算が必要な場合、IntlCharクラスの使用や、実際のレンダリング結果を測定する方法を検討する必要があります。
  • ただし、これらの方法はより複雑で、実装コストが高くなる可能性があります。

推奨アプローチ:

  • まずはmb_strwidthを使用し、問題が生じた特定のケースに対してのみ、より複雑な解決策を適用することを推奨します。
  • アプリケーションの要件と対象ユーザーを考慮し、必要に応じて精度と実装の簡便さのバランスを取ることが重要です。
スポンサーリンク

mb_strwidthの文字化けで使えない?

この文字化け問題でハマったことがあります。コードが複雑だったため原因究明に時間がかかりました。

サンプルです。このまま使うことはしませんでしたが、学習を助ける雛形になるかもですね。

    // 幅カウントで 1 文字ずつ積み上げる実装 ---

    // この実装は「1文字ずつ幅を数える」素朴版です。半角=1、全角=2 の表示幅を足し上げ、
    // 合計が 36 を超える直前で改行します。直感的で読みやすい一方、1文字ごとの処理で遅く、
    // 長文や大量生成には不向きです。複合絵文字は見た目1文字でも分割される場合があります。
    // (厳密に扱うなら PHP の intl 拡張の grapheme_* 系を検討してください。)
    $lines = [];
    $current_line = '';
    $current_width = 0;    

    // 入力テキストを「改行」で分割し、意図した改行はそのまま残す。
    // 以降は 1 行ずつ「最大36幅」に収める処理を行う。

    // 改行コードで分割
    $input_lines = preg_split('/\R/', $text);

    // 各行を順番に処理する。
    // 1 文字ずつ取り出して表示幅を加算し、超えそうになったら改行する。
    foreach ($input_lines as $line) {
        for ($i = 0; $i < mb_strlen($line, $encoding); $i++) {
            // 現在位置の 1 文字を取得(マルチバイト対応)。
            $char = mb_substr($line, $i, 1, $encoding);
            // その文字の「表示幅」を取得。半角=1、全角=2。
            $char_width = mb_strwidth($char, $encoding);

            // これ以上追加すると 36 幅を超える → いったん行を確定して改行する。
            // (バッファに貯めていた current_line を出力配列に入れ、
            //   その後で現在の文字から新しい行を開始する。)
            if ($current_width + $char_width > $max_width) {
                $lines[] = $current_line;
                $current_line = $char;
                $current_width = $char_width;
            } else {
                $current_line .= $char;
                $current_width += $char_width;
            }
        }

        // 行末に到達:バッファに残っている文字列があれば最終行として追加する。
        if ($current_line !== '') {
            $lines[] = $current_line;
            $current_line = '';
            $current_width = 0;
        }
    }
    // すべての行を返す。
    return $lines;

mb_substrmb_strwidthから、mb_strimwidthmb_strlenへの置き換えることにより回避しました。

mb_substr($string, $start, $length, $encoding)

  • マルチバイト文字列から、指定した位置($start)から指定した長さ($length)の部分文字列を取得します。
  • 文字数を基準に部分文字列を取得します。

mb_strwidth($string, $encoding)

  • 文字列の表示幅を計算します。半角文字を1、全角文字を2としてカウントします。
  • 表示上の幅を基準に計算します。

mb_strimwidth($string, $start, $width, $trimmarker, $encoding)

  • 指定した表示幅($width)に収まるように文字列を切り取ります。
  • 文字の途中で切り取らず、安全に表示幅を制限できます。最大の部分文字列を一発で返します。
  • $trimmarker を指定すると、切り取った後にその文字列を追加できます(今回は空文字列を指定)。

mb_strlen($string, $encoding)

  • マルチバイト文字列の文字数を取得します。
  • 文字数を基準に長さを計算します。

簡単なサンプルです。

// ======= mb_substr =======
// 文字列から指定した位置から指定した長さの部分文字列を取得
$text = "こんにちは世界!";
echo mb_substr($text, 0, 3); // => "こんに"
echo mb_substr($text, 3, 2); // => "ちは"
echo mb_substr($text, -2);   // => "界!" (負の位置は末尾からカウント)

// ======= mb_strwidth =======
// 文字列の表示幅を取得(半角1、全角2としてカウント)
$text = "Hello世界!";
echo mb_strwidth($text); // => 8
// H(1) + e(1) + l(1) + l(1) + o(1) + 世(2) + 界(2) + !(2) = 11

// ======= mb_strimwidth =======
// 指定した表示幅に収まるように文字列を切り取り、必要に応じて末尾文字列を追加
$text = "とても長い文章です。最後まで読めません。";
echo mb_strimwidth($text, 0, 10, "..."); // => "とても長い..."
echo mb_strimwidth($text, 0, 15, "→続く"); // => "とても長い文章→続く"

mb_strimwidth文字化けする場合は第五引数で文字コードを設定して回避するとよさそうです。

エンコードを指定したサンプルです。

<?php
// UTF-8文字列のサンプル
$text = "こんにちは、World!";

// mb_strwidth: 文字列の表示幅を取得(半角1、全角2としてカウント)
$width = mb_strwidth($text, "UTF-8");
echo "mb_strwidth の結果: " . $width . "\n";  // 結果: 14 (全角5文字×2 + 半角6文字×1)

// mb_strimwidth: 指定した表示幅に文字列を切り詰め
$trimmed = mb_strimwidth($text, 0, 10, "...", "UTF-8");
echo "mb_strimwidth の結果: " . $trimmed . "\n";  // 結果: "こんにち..."

// 実践的な使用例:表示幅が異なる場合の比較
$strings = ["Hello", "こんにちは", "Hello!"];
foreach ($strings as $str) {
    echo sprintf(
        "文字列: %s, 長さ: %d, 表示幅: %d\n",
        $str,
        mb_strlen($str, "UTF-8"),
        mb_strwidth($str, "UTF-8")
    );
}

ご参考になれば幸いです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする