PHP Tips | 文字列処理:ログ・表示向け - エラーメッセージ整形

PHP PHP
スポンサーリンク

なぜ「エラーメッセージ整形」が必要なのか

エラーメッセージって、放っておくとすぐに「バラバラ」になります。

同じようなエラーなのに、
「必須です」「入力してください」「この項目は必須です」
みたいに表現が揺れたり。

ログに出すメッセージと、画面に出すメッセージがごちゃ混ぜになったり。
例外オブジェクトのメッセージをそのままユーザーに見せてしまったり。

こういう状態だと、
ユーザーには伝わりにくいし、運用側もログを追いづらいし、
セキュリティ的にも危ない(内部情報を出しすぎる)ことがあります。

そこで、「エラーメッセージを“整形してから”使う」ユーティリティを用意しておくと、
実務のコードがかなりスッキリします。


まず「誰向けのメッセージか」を分けて考える

ユーザー向けと開発者向けは別物

エラーメッセージには、大きく 2 種類あります。

ユーザー向け(画面に出す)
開発者・運用者向け(ログに出す)

この 2 つは、目的がまったく違います。

ユーザー向けは、「何が起きたか」「どうすればいいか」をやさしく伝える。
開発者向けは、「どこで」「どんな例外が」「どんな入力で」起きたかを詳しく残す。

なので、同じエラーでも「メッセージの中身」と「整形の仕方」を変える必要があります。
ここをちゃんと分けるのが、エラーメッセージ整形ユーティリティの一番大事なポイントです。


ユーザー向けエラーメッセージ整形

プレーンなメッセージを「画面表示用」に整える

ユーザー向けのメッセージは、だいたいこんな流れで整形します。

余計な改行や空白を整える
危険な文字を HTML エスケープする
複数行なら <br> に変換する

これを毎回書くのではなく、関数にまとめます。

/**
 * ユーザー向けエラーメッセージを画面表示用に整形する
 *
 * - 前後の空白をトリム
 * - 改行以外の制御文字を削除
 * - HTML エスケープ
 * - 改行を <br> に変換
 */
function format_error_message_for_view(string $message): string
{
    $message = trim($message);

    // 改行(LF, CR)は残し、それ以外の制御文字を削除
    $message = preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', '', $message) ?? '';

    // HTML エスケープ
    $escaped = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');

    // 改行を <br> に
    return nl2br($escaped);
}
PHP

使い方の例です。

$userMessage = "メールアドレスを入力してください。\nもう一度お試しください。";

echo format_error_message_for_view($userMessage);
PHP

画面上では、次のように表示されます。

メールアドレスを入力してください。
もう一度お試しください。

ここでの重要ポイントは、「エスケープと改行処理の順番」です。
先に htmlspecialchars でエスケープしてから nl2br をかけることで、
XSS を防ぎつつ、見た目も整ったメッセージになります。


ログ向けエラーメッセージ整形

例外情報を「1 行で読みやすく」する

ログに出すエラーメッセージは、
「例外クラス名」「メッセージ」「コード」「発生場所」などをまとめて 1 行にしたいことが多いです。

/**
 * 例外をログ向けの 1 行メッセージに整形する
 */
function format_exception_for_log(Throwable $e): string
{
    // ベースとなるメッセージ
    $base = sprintf(
        '%s: %s (code=%d) at %s:%d',
        get_class($e),
        $e->getMessage(),
        $e->getCode(),
        $e->getFile(),
        $e->getLine()
    );

    // 制御文字を削除し、長すぎる場合は省略
    $base = preg_replace('/[\x00-\x1F\x7F]/', '', $base) ?? '';

    if (mb_strlen($base, 'UTF-8') > 500) {
        $base = mb_substr($base, 0, 500, 'UTF-8') . '...';
    }

    return $base;
}
PHP

使い方の例です。

try {
    // 何か処理
} catch (Throwable $e) {
    error_log(format_exception_for_log($e));
}
PHP

ログには、例えばこんな感じで出ます。

RuntimeException: DB connection failed (code=0) at /var/www/app/Db.php:123

ここでの重要ポイントは、「ログ用のメッセージは 1 行で完結させる」ことです。
スタックトレースは別途 error_log($e) などで出してもいいですが、
一覧性の高い 1 行メッセージがあると、後から追うときに圧倒的に楽になります。


バリデーションエラーの整形

複数のエラーを「1 本のメッセージ」にまとめる

フォームのバリデーションでは、
「メールアドレスが必須」「パスワードが短すぎる」など、複数のエラーが出ることがあります。

内部的には配列で持っておき、
画面に出すときに「箇条書き」や「1 行メッセージ」に整形します。

/**
 * バリデーションエラー配列を、画面表示用の HTML に整形する
 *
 * 例:
 * ['メールアドレスは必須です', 'パスワードが短すぎます']
 * →
 * メールアドレスは必須です<br>パスワードが短すぎます
 */
function format_validation_errors_for_view(array $errors): string
{
    if ($errors === []) {
        return '';
    }

    // 1 行ずつ整形
    $lines = [];

    foreach ($errors as $msg) {
        $lines[] = strip_tags(
            // 念のため、ユーザー入力が混ざっていても安全なように
            htmlspecialchars((string)$msg, ENT_QUOTES, 'UTF-8')
        );
    }

    // <br> でつなぐ
    return implode('<br>', $lines);
}
PHP

使い方の例です。

$errors = [
    'メールアドレスは必須です',
    'パスワードが短すぎます',
];

echo format_validation_errors_for_view($errors);
PHP

画面上では、次のように表示されます。

メールアドレスは必須です
パスワードが短すぎます

ここでのポイントは、「配列 → 表示用文字列」の変換を 1 箇所に閉じ込めることです。
ビュー側では「この関数を呼ぶだけ」で済むようにしておくと、コードがかなりスッキリします。


「内部メッセージ」と「外部メッセージ」を分ける設計

例外メッセージをそのままユーザーに見せない

実務でやりがちなのが、
$e->getMessage() をそのままユーザーに見せてしまうパターンです。

これは危険です。

SQL の内容やファイルパスなど、内部構造がそのまま出てしまうことがあります。
攻撃者にとっては「ごちそう」です。

なので、設計としてはこう分けます。

内部メッセージ(例外メッセージ)はログにだけ出す。
ユーザーには「汎用的なメッセージ」を別途用意しておく。

例えば、こんな感じです。

try {
    // 何か処理
} catch (Throwable $e) {
    // ログには詳細を出す
    error_log(format_exception_for_log($e));

    // ユーザーには汎用メッセージだけ
    $userMessage = 'システムエラーが発生しました。時間をおいて再度お試しください。';

    echo format_error_message_for_view($userMessage);
}
PHP

ここでの重要ポイントは、「ログ用」と「画面用」を意図的に分けていることです。
エラーメッセージ整形ユーティリティは、その「分ける設計」を支える道具になります。


まとめ:今日からの「エラーメッセージ整形」ユーティリティ

エラーメッセージ整形の本質は、「誰に何をどこまで見せるか」をコードでコントロールすることです。

ユーザー向けには、短く・分かりやすく・安全に。
ログ向けには、詳しく・機械的に扱いやすく・1 行で。

そのために、

ユーザー向け:format_error_message_for_view
ログ向け:format_exception_for_log
バリデーション用:format_validation_errors_for_view

のような関数を用意しておき、「エラーを出すときは必ずどれかを通す」と決めてしまう。
これだけで、エラーメッセージ周りの品質と安全性は一気に上がります。

もし、あなたのコードのどこかで「echo $e->getMessage();」や「die($e->getMessage());」のような行があれば、そこがこのユーティリティに置き換えるべきポイントです。
エラーメッセージを“整形してから出す”という一歩が、プロダクトの「大人度」をぐっと上げてくれます。

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