PHP Tips | 文字列処理:文字数・切り出し – バイト数で切り出し

PHP PHP
スポンサーリンク

「バイト数で切り出し」とは何をしているのか

ここまでずっと「文字数」で安全に扱う話をしてきましたが、
今回のテーマはあえて逆方向——「バイト数で切り出す」です。

文字数ではなく、「何バイトまで」という単位で文字列を切りたい。

例えば、外部システムとの連携仕様で、

  • 「この項目は最大 20バイトまで」
  • 「UTF-8 だけど、バイト数で切って送ってほしい」

といったルールが決まっていることがあります。
このときに必要になるのが「バイト数で切り出し」です。

ただし、ここで一番大事なのは、

「バイト数で切る」と「文字が途中で切れて壊れる」可能性がある

ということを、ちゃんと理解したうえで使うことです。


substr でバイト数切り出しをすると何が起きるか

substr は「バイト単位」で動く

PHP の substr() は、もともと「バイト列」を相手にする関数です。
UTF-8 の日本語をそのまま渡すと、こうなります。

$text = "こんにちは"; // UTF-8

echo substr($text, 0, 3);  // 先頭3バイト
echo "\n";
echo substr($text, 0, 4);  // 先頭4バイト
PHP

UTF-8 では「こ」は 3バイトなので、

  • 3バイト切る → たまたま「こ」だけになる
  • 4バイト切る → 「こ」の途中+次の文字の一部、という中途半端な状態になる

結果として、後者は文字化けしたり、表示できない文字になったりします。

つまり、substr をそのまま「日本語テキスト」に使うのは、基本的に危険です。


「バイト数で切りたい」+「できるだけ文字を壊したくない」

そこで出てくるのが mb_strcut

「バイト数で切りたいんだけど、できれば文字の途中では切りたくない」
という、ちょっとワガママな要望に応えてくれるのが mb_strcut() です。

mb_strcut は、

  • 「バイト数」で位置や長さを指定する
  • ただし、マルチバイト文字の途中では切らないように調整してくれる

という関数です。

mb_strcut の基本

シグネチャはこうです。

string mb_strcut(
    string $string,
    int $start,
    ?int $length = null,
    ?string $encoding = null
)
PHP
  • 第2引数:開始バイト位置
  • 第3引数:取得するバイト数
  • 第4引数:エンコーディング('UTF-8' を明示するのが基本)

mb_strcut と substr の違いを体感する

実験コード

$input = "こんにちは"; // UTF-8

// mb_strcut を使う例
$cut1 = mb_strcut($input, 0, 7, "UTF-8"); // 7バイトまで
$cut2 = mb_strcut($input, 0, 6, "UTF-8"); // 6バイトまで
$cut3 = mb_strcut($input, 0, 5, "UTF-8"); // 5バイトまで

// substr を使う例
$substr1 = substr($input, 0, 7);
$substr2 = substr($input, 0, 6);
$substr3 = substr($input, 0, 5);

var_dump($cut1, $cut2, $cut3);
var_dump($substr1, $substr2, $substr3);
PHP

典型的には、こんな挙動になります。

  • mb_strcut
    • 5バイト → 「こ」
    • 6バイト → 「こ」
    • 7バイト → 「こん」
      → 文字の途中では切らないように、うまく調整してくれる
  • substr
    • 5バイト → 「こ+次の文字の一部」→ 文字化け
    • 6バイト → 同様に中途半端
      → バイトだけ見て切るので、文字の境界を気にしない

ここから分かるのは、

「バイト数で切りたい」+「文字化けはできるだけ避けたい」
なら、substr ではなく mb_strcut を使うべき

ということです。


実務ユーティリティ:バイト数で切り出す関数

完成形を先に出す

/**
 * バイト数で切り出し(マルチバイト文字を途中で切らない・UTF-8 前提)
 *
 * @param string   $text    対象文字列
 * @param int      $start   開始バイト位置
 * @param int|null $length  取得するバイト数(null なら最後まで)
 * @return string           切り出した文字列
 */
function cutByBytes(string $text, int $start, ?int $length = null): string
{
    // エンコーディングは UTF-8 を明示
    $encoding = 'UTF-8';

    // 長さが null の場合は「最後まで」
    if ($length === null) {
        return mb_strcut($text, $start, null, $encoding);
    }

    // 負の値などはここでガードしておく(方針次第)
    if ($length < 0) {
        return '';
    }

    return mb_strcut($text, $start, $length, $encoding);
}
PHP

使い方のイメージ

$text = "これはサンプルの文章です";

// 先頭から 10バイトだけ
echo cutByBytes($text, 0, 10);

// 5バイト目から 8バイト分
echo cutByBytes($text, 5, 8);
PHP

ここでのポイントは、

  • 「バイト数」で指定している
  • でも mb_strcut が「文字の途中で切らないように」調整してくれる

というところです。


どんな場面で「バイト数で切り出し」が必要になるか

例題1:外部システムとの連携仕様が「バイト数」前提

古いシステムや他言語のシステムと連携するときに、
「この項目は 20バイトまで」という仕様が残っていることがあります。

$name = $row['name']; // ユーザー名(日本語含む)

// 20バイトまでに収める
$fixedName = cutByBytes($name, 0, 20);

// あとは固定長ファイルや CSV に出力
PHP

ここで substr を使うと、日本語が途中で切れて文字化けしたり、
相手側システムでエラーになったりする可能性があります。

mb_strcut ベースの cutByBytes を使えば、

  • バイト数の制約は守る
  • できるだけ文字の途中では切らない

という、現実的な落としどころが作れます。

例題2:ストレージやキー長の制限が「バイト数」な場合

データベースやキャッシュキーなどで、

  • 「このカラムは最大 50バイト」
  • 「キーは 250バイトまで」

といった制限がある場合も、バイト数で切る必要が出てきます。

$key = "user:" . $userId . ":" . $someLongString;

// 250バイト以内に収める
$key = cutByBytes($key, 0, 250);
PHP

ここでも、「英数字だけなら substr でもいい」が、
日本語や絵文字が混ざる可能性があるなら、最初から mb_strcut に統一しておく方が安全です。


重要な注意点:「バイト数で切る」こと自体は危険でもある

表示用テキストには基本的に使わない

ここまで「バイト数で切る」話をしてきましたが、
これはあくまで「仕様としてバイト数が求められている場面」限定のテクニックです。

画面表示用のテキスト(タイトル・本文など)を、

  • 見た目を整えたい
  • 文字数制限をしたい

という理由で切るときに、「バイト数で切る」のは基本的に NG です。

その場合は、

  • 文字数で切る → mb_substr
  • 単語単位で切る → 前回の「単語単位で省略」

といったアプローチを取るべきです。

「バイト数で切る」ときは、目的をはっきりさせる

なので、こう整理しておくと迷いません。

  • 「人間が読むテキストの長さを制御したい」
    → 文字数ベース(mb_substr
  • 「外部仕様やプロトコルがバイト数で決まっている」
    → バイト数ベース(mb_strcut

この切り分けを、チーム内でも共有しておくと、
substr の誤用によるバグをかなり減らせます。


まとめ:今日からの「バイト数で切り出し」の基準

最後に、要点だけコンパクトにまとめます。

  • substr は「バイト単位」で切るが、マルチバイト文字を平気で途中で切るので、日本語には危険。
  • 「バイト数で切りたい」+「できるだけ文字を壊したくない」ときは、mb_strcut を使う。
  • 表示用テキストの長さ制御には使わず、あくまで「バイト数が仕様で決まっている場面」に限定する。

実務ユーティリティとしては、次のような関数を一つプロジェクトに置いておくと便利です。

function cutByBytes(string $text, int $start, ?int $length = null): string
{
    $encoding = 'UTF-8';

    if ($length === null) {
        return mb_strcut($text, $start, null, $encoding);
    }

    if ($length < 0) {
        return '';
    }

    return mb_strcut($text, $start, $length, $encoding);
}
PHP

タイトルとURLをコピーしました