なぜ「エラーメッセージ整形」が必要なのか
エラーメッセージって、放っておくとすぐに「バラバラ」になります。
同じようなエラーなのに、
「必須です」「入力してください」「この項目は必須です」
みたいに表現が揺れたり。
ログに出すメッセージと、画面に出すメッセージがごちゃ混ぜになったり。
例外オブジェクトのメッセージをそのままユーザーに見せてしまったり。
こういう状態だと、
ユーザーには伝わりにくいし、運用側もログを追いづらいし、
セキュリティ的にも危ない(内部情報を出しすぎる)ことがあります。
そこで、「エラーメッセージを“整形してから”使う」ユーティリティを用意しておくと、
実務のコードがかなりスッキリします。
まず「誰向けのメッセージか」を分けて考える
ユーザー向けと開発者向けは別物
エラーメッセージには、大きく 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());」のような行があれば、そこがこのユーティリティに置き換えるべきポイントです。
エラーメッセージを“整形してから出す”という一歩が、プロダクトの「大人度」をぐっと上げてくれます。
