「バイト数で切り出し」とは何をしているのか
ここまでずっと「文字数」で安全に扱う話をしてきましたが、
今回のテーマはあえて逆方向——「バイト数で切り出す」です。
文字数ではなく、「何バイトまで」という単位で文字列を切りたい。
例えば、外部システムとの連携仕様で、
- 「この項目は最大 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バイト
PHPUTF-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