PHP Tips | 文字列処理:文字数・切り出し – 単語単位で省略

PHP PHP
スポンサーリンク

「単語単位で省略」がやりたいこと

これまでの「指定文字数で省略」は、「文字数」でバッサリ切るものでした。
今回の「単語単位で省略」は、もう一歩だけ気を遣った省略です。

文章を途中でブツッと切るのではなく、
「単語の途中では切らずに」
指定した長さの範囲で、きれいなところまで省略したい。

例えば、英語のタイトルがこうだとします。

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

なので、

  • スペースで分割する
  • 単語を順に足していき、指定の長さを超えないところまで残す

というロジックが素直に書けます。

一方、日本語はスペースで区切られないことが多いので、
「単語単位で省略」という発想は、主に英語や欧文テキスト向けと考えるのが現実的です。

ここでは、

「英語など、スペース区切りの文章を単語単位で省略する」

という前提で話を進めます。


単語単位で省略する基本アルゴリズム

やりたいことを手順にすると

やりたいことを、あえて人間の作業に落とすとこうなります。

  1. 文章をスペースで単語に分ける
  2. 先頭から単語を一つずつ足していく
  3. 「次の単語を足したら上限を超える」タイミングで止める
  4. そこまでを結合して、「…」などを付ける(付けなくてもよい)

この流れをそのまま 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);
PHP

preg_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;
PHP

suffix'…''...' を渡せば省略記号付き、
空文字 '' を渡せば「省略記号なし」にできます。

例えば、「単語単位で省略するけれど、省略記号はいらない」なら、こう呼び出します。

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

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