「単語単位で省略」がやりたいこと
これまでの「指定文字数で省略」は、「文字数」でバッサリ切るものでした。
今回の「単語単位で省略」は、もう一歩だけ気を遣った省略です。
文章を途中でブツッと切るのではなく、
「単語の途中では切らずに」
指定した長さの範囲で、きれいなところまで省略したい。
例えば、英語のタイトルがこうだとします。
This is a very long article title about PHP utilities
これを「20文字くらいまで」にしたいとき、
文字数だけで切るとこうなります。
This is a very long a…
単語の途中で切れていて、ちょっと不格好ですよね。
単語単位で省略したい場合は、こうしたいわけです。
This is a very long…
「単語の途中では切らない」というのが、今回のユーティリティのポイントです。
まず前提:「単語」とは何かを決める
英語と日本語で考え方が違う
英語の場合、「単語」はスペースで区切られます。
This / is / a / pen
なので、
- スペースで分割する
- 単語を順に足していき、指定の長さを超えないところまで残す
というロジックが素直に書けます。
一方、日本語はスペースで区切られないことが多いので、
「単語単位で省略」という発想は、主に英語や欧文テキスト向けと考えるのが現実的です。
ここでは、
「英語など、スペース区切りの文章を単語単位で省略する」
という前提で話を進めます。
単語単位で省略する基本アルゴリズム
やりたいことを手順にすると
やりたいことを、あえて人間の作業に落とすとこうなります。
- 文章をスペースで単語に分ける
- 先頭から単語を一つずつ足していく
- 「次の単語を足したら上限を超える」タイミングで止める
- そこまでを結合して、「…」などを付ける(付けなくてもよい)
この流れをそのまま PHP に落とし込めば、単語単位の省略が書けます。
実装例:英語向け「単語単位で省略」関数
まずは完成形を見てみる
シンプルで実務的な関数を先に出します。
/**
* 単語単位で省略(英語などスペース区切り前提・UTF-8)
*
* @param string $text 対象文字列
* @param int $limit 最大文字数
* @param string $suffix 省略時に末尾に付ける文字(例: '…' や '...'。不要なら空文字)
* @return string
*/
function truncateByWords(string $text, int $limit, string $suffix = '…'): string
{
// 前後の空白を整える
$text = trim($text);
// そもそも短ければそのまま返す
if (mb_strlen($text, 'UTF-8') <= $limit) {
return $text;
}
// スペースで単語に分割(連続スペースもまとめて扱う)
$words = preg_split('/\s+/', $text);
$result = '';
$currentLength = 0;
foreach ($words as $index => $word) {
// 単語の長さ
$wordLength = mb_strlen($word, 'UTF-8');
// 先頭以外の単語にはスペースが1つ入る
$extraSpace = ($index === 0) ? 0 : 1;
// この単語を追加したときの長さを計算
$nextLength = $currentLength + $extraSpace + $wordLength;
// 上限を超えるなら、ここで打ち切り
if ($nextLength > $limit) {
break;
}
// 実際に単語を追加
if ($index === 0) {
$result = $word;
} else {
$result .= ' ' . $word;
}
$currentLength = $nextLength;
}
// もし result が空(最初の単語すら入らない)なら、先頭から limit 文字だけ切る
if ($result === '') {
$result = mb_substr($text, 0, $limit, 'UTF-8');
}
// 省略記号を付ける(不要なら suffix を '' にする)
return $result . $suffix;
}
PHP使い方の例
$title = "This is a very long article title about PHP utilities";
echo truncateByWords($title, 20);
// This is a very long…
PHP関数の中身をかみ砕いて理解する
1. まず「短いならそのまま返す」
if (mb_strlen($text, 'UTF-8') <= $limit) {
return $text;
}
PHPここは、前回までの「文字数で省略」と同じ考え方です。
- そもそも短い文字列は、わざわざ省略する必要がない
- 「長すぎるときだけ省略する」のが自然
なので、「制限以内ならそのまま返す」という分岐を必ず入れます。
2. スペースで単語に分割する
$words = preg_split('/\s+/', $text);
PHPpreg_split('/\s+/', ...) は、
- 1つ以上の空白文字(スペース、タブ、改行など)を区切りとして
- 文字列を分割する
という意味です。
これで、例えば
"This is a pen"
のようにスペースがバラバラでも、
["This", "is", "a", "pen"]
PHPのようにきれいに単語配列にできます。
3. 単語を一つずつ足していく
$result = '';
$currentLength = 0;
foreach ($words as $index => $word) {
$wordLength = mb_strlen($word, 'UTF-8');
$extraSpace = ($index === 0) ? 0 : 1;
$nextLength = $currentLength + $extraSpace + $wordLength;
if ($nextLength > $limit) {
break;
}
if ($index === 0) {
$result = $word;
} else {
$result .= ' ' . $word;
}
$currentLength = $nextLength;
}
PHPここが「単語単位で省略」の心臓部です。
やっていることを言葉にすると、
- 今までに積み上げた長さが
$currentLength - 次の単語の長さが
$wordLength - 先頭以外の単語にはスペースが 1 文字入るので、それも足す
- それを全部足した長さが
$nextLength - もし
$nextLengthが上限$limitを超えるなら、そこで打ち切る - 超えないなら、実際に単語を
$resultに追加する
という流れです。
「スペースも文字数に含める」というのがポイントです。
単語だけでなく、「単語+単語の間のスペース」も含めて、全体の長さを管理しています。
4. どの単語も入らない場合の保険
if ($result === '') {
$result = mb_substr($text, 0, $limit, 'UTF-8');
}
PHP例えば、$limit が極端に小さい(1 文字など)場合、
最初の単語すら入らないことがあります。
その場合に何も返さないのは不便なので、
「単語単位では切れないけれど、とりあえず先頭から
$limit文字だけ返す」
という保険を入れています。
5. 省略記号を付けるかどうか
return $result . $suffix;
PHPsuffix に '…' や '...' を渡せば省略記号付き、
空文字 '' を渡せば「省略記号なし」にできます。
例えば、「単語単位で省略するけれど、省略記号はいらない」なら、こう呼び出します。
echo truncateByWords($title, 20, '');
// This is a very long
PHP実務での具体的なシチュエーション例
例題1:英語ブログのタイトル一覧
英語のブログやドキュメントサイトで、
「タイトルが長すぎると崩れるので、20文字くらいで単語単位に切りたい」というケース。
$title = $row['title'];
$shortTitle = truncateByWords($title, 20, '…');
echo htmlspecialchars($shortTitle, ENT_QUOTES, 'UTF-8');
PHPこれで、
- 単語の途中では切らない
- きれいな単語の区切りで「…」を付ける
という、見た目の良い省略ができます。
例題2:英語の説明文をカードレイアウトに収める
カード型の UI で、英語の説明文を短く見せたいとき。
$description = $row['description'];
$shortDesc = truncateByWords($description, 80, '…');
echo nl2br(htmlspecialchars($shortDesc, ENT_QUOTES, 'UTF-8'));
PHP文字数だけで切るよりも、「単語単位で切る」方が、読みやすさがぐっと上がります。
日本語と「単語単位で省略」について少しだけ
日本語はスペースで区切られないので、
英語のように「単語単位で省略」というのは簡単ではありません。
本気でやるなら、
- 形態素解析(MeCab など)で単語に分割する
- その結果を使って単語単位で省略する
といったアプローチになりますが、これはもう「ユーティリティ関数」の範囲を超えた世界です。
実務では、
- 日本語:
mb_substrで「文字数単位の省略」 - 英語など:今回のような「単語単位の省略」
という使い分けをするのが現実的です。
まとめ:今日からの「単語単位で省略」の基準
押さえておきたいポイントをコンパクトにまとめると、こうなります。
- 「単語単位で省略」は、主に英語などスペース区切りの文章向け。
- スペースで単語に分割し、「次の単語を足したら上限を超えるか?」を見ながら積み上げていく。
- 省略記号を付けるかどうかは、呼び出し側で選べるようにしておくと便利。
そして、実務ユーティリティとしては、この関数を一つプロジェクトに置いておくとかなり使い回せます。
function truncateByWords(string $text, int $limit, string $suffix = '…'): string
{
$text = trim($text);
if (mb_strlen($text, 'UTF-8') <= $limit) {
return $text;
}
$words = preg_split('/\s+/', $text);
$result = '';
$currentLength = 0;
foreach ($words as $index => $word) {
$wordLength = mb_strlen($word, 'UTF-8');
$extraSpace = ($index === 0) ? 0 : 1;
$nextLength = $currentLength + $extraSpace + $wordLength;
if ($nextLength > $limit) {
break;
}
if ($index === 0) {
$result = $word;
} else {
$result .= ' ' . $word;
}
$currentLength = $nextLength;
}
if ($result === '') {
$result = mb_substr($text, 0, $limit, 'UTF-8');
}
return $result . $suffix;
}
PHP