PHPでメッセージ管理を統一する設計:info / warning / error を一元管理する MessageHandler の実装方法
篠原 隆司
アフィリエイト広告を利用しています
このページの内容が役に立ったら X (旧twitter) でフォローして頂けると励みになります
挨拶や報告は無しで大丈夫です
PHP では、バリデーションや保存処理、API 通信など、成功・失敗・注意のメッセージを返す場面が必ず発生します。これを毎回クラスごとに実装すると、コードが散らかり、分類も曖昧になり、後から見て全く整合性が取れなくなります。本記事では、メッセージ処理を info / warning / error の 3 種類に統一し、MessageHandler という専用クラスに一元化する方法を解説します。
結論
メッセージ処理は共通化しないと確実に破綻する
バリデーション、保存処理、API 通信など、メッセージはどのクラスでも必ず使います。これを毎回書くのは無駄であり、整合性も取れません。共通化は必須です。
継承で共通化するのは間違い
「メッセージ処理を基底クラスに入れて継承すればいい」と考える人が多いです。しかし継承は密結合を生み、本来不要なメソッドを派生クラス全てに押し付けます。責務が混ざり、変更も困難です。
正しい設計は Composition(プロパティで保持)
MessageHandler のような横断的機能は「持つ(has-a)」設計が正解です。差し替えやすく、テストしやすく、責務が混ざりません。
3分類(info / warning / error)で統一すべき理由
- 成功(info)
- 注意(warning)
- 失敗(error)
実務では warning が存在しない設計は不自然で、後から UI を作る際に破綻します。
MessageHandler が解決する問題
- 散らかったメッセージ処理を集約できる
- UI とロジックを分離できる
- どのクラスでも同じ形式で扱える
- ログや JSON などへの拡張が容易
背景
メッセージは全クラスで必ず発生する
成功通知、注意喚起、エラー表示。これらを統一できないと可読性が落ち、プロジェクト全体の品質に直結します。
ありがちな誤解:継承すれば楽になる
一見便利に見えますが、継承は不要な責務を巻き込み、後から絶対に面倒になります。
継承が破綻する理由
- メッセージ処理と業務ロジックが混ざる
- 意図しないメソッドが派生クラスへ漏れる
- 変更の影響範囲が広すぎる
Composition にすることで責務が明確になる
MessageHandler は「メッセージ処理だけ」を担当し、業務クラスはロジックに集中できます。
基礎:info / error の 2分類では足りない理由
info と error の切り分けだけでも改善はされる
成功と失敗を分けるだけでも多少整理されます。これは最初の一歩としては正しいです。
しかし実務では warning が必須
- 入力は正常だが補正した
- 通信は成功したが品質が低い
- 保存したがいくつかのレコードのみ成功した
warning がないと UI が作れない
warning がない設計は、閲覧者に正しい状況を伝えられません。UX を損ないます。
基礎版コードはここでは省略
この記事では最終形である「3分類版 MessageHandler」を直接採用します。
設計:3分類 MessageHandler(info / warning / error)
warning が重要になる実務ケース
多くの現場では「エラーではないが、注意すべき状況」が山ほどあります。warning を導入するだけでロジックが明確化します。
3分類化のメリット
- 利用者に優しい UI が作れる
- 開発側のロジックが明確になる
- ログや API の構造にも使いやすい
- メッセージ分類の曖昧さが消える
MessageHandler の責務は「分類と保持」だけ
業務処理や HTML の責務を混ぜないことが重要です。
3分類 MessageHandler(PSR-12 + phpDoc 完全版)
<?php
declare(strict_types=1);
namespace App\Core;
/**
* Class MessageHandler
*
* info / warning / error の 3 種類のメッセージを一元管理するクラス。
* クラス横断的に発生するメッセージ処理を統一し、責務を分離する目的で利用する。
*/
class MessageHandler
{
/**
* @var array<int, string> 通常メッセージ一覧
*/
private array $info = [];
/**
* @var array<int, string> 警告メッセージ一覧
*/
private array $warning = [];
/**
* @var array<int, string> エラーメッセージ一覧
*/
private array $error = [];
/**
* info(通常メッセージ)を追加する。
*
* @param string $message 追加するメッセージ
* @return void
*/
public function addInfo(string $message): void
{
$normalized = trim($message);
if ($normalized !== '') {
$this->info[] = $normalized;
}
}
/**
* warning(注意メッセージ)を追加する。
*
* @param string $message 追加するメッセージ
* @return void
*/
public function addWarning(string $message): void
{
$normalized = trim($message);
if ($normalized !== '') {
$this->warning[] = $normalized;
}
}
/**
* error(エラーメッセージ)を追加する。
*
* @param string $message 追加するメッセージ
* @return void
*/
public function addError(string $message): void
{
$normalized = trim($message);
if ($normalized !== '') {
$this->error[] = $normalized;
}
}
/**
* info(通常メッセージ)を取得する。
*
* @return array<int, string>
*/
public function getInfo(): array
{
return $this->info;
}
/**
* warning(注意メッセージ)を取得する。
*
* @return array<int, string>
*/
public function getWarning(): array
{
return $this->warning;
}
/**
* error(エラーメッセージ)を取得する。
*
* @return array<int, string>
*/
public function getError(): array
{
return $this->error;
}
/**
* info が存在するか。
*
* @return bool
*/
public function hasInfo(): bool
{
return count($this->info) > 0;
}
/**
* warning が存在するか。
*
* @return bool
*/
public function hasWarning(): bool
{
return count($this->warning) > 0;
}
/**
* error が存在するか。
*
* @return bool
*/
public function hasError(): bool
{
return count($this->error) > 0;
}
/**
* info を HTML リストで返す。
*
* @return string
*/
public function renderInfoHtml(): string
{
if ($this->hasInfo() === false) {
return '';
}
$html = '';
foreach ($this->info as $msg) {
$html .= '<li><span class="msg-info">' . htmlspecialchars($msg, ENT_QUOTES) . '</span></li>';
}
return '<ul class="msg-list info">' . $html . '</ul>';
}
/**
* warning を HTML リストで返す。
*
* @return string
*/
public function renderWarningHtml(): string
{
if ($this->hasWarning() === false) {
return '';
}
$html = '';
foreach ($this->warning as $msg) {
$html .= '<li><span class="msg-warning">' . htmlspecialchars($msg, ENT_QUOTES) . '</span></li>';
}
return '<ul class="msg-list warning">' . $html . '</ul>';
}
/**
* error を HTML リストで返す。
*
* @return string
*/
public function renderErrorHtml(): string
{
if ($this->hasError() === false) {
return '';
}
$html = '';
foreach ($this->error as $msg) {
$html .= '<li><span class="msg-error">' . htmlspecialchars($msg, ENT_QUOTES) . '</span></li>';
}
return '<ul class="msg-list error">' . $html . '</ul>';
}
/**
* error → warning → info の順でまとめて HTML を返す。
*
* @return string
*/
public function renderAllHtml(): string
{
return $this->renderErrorHtml()
. $this->renderWarningHtml()
. $this->renderInfoHtml();
}
/**
* すべてのメッセージをクリアする。
*
* @return void
*/
public function clearAll(): void
{
$this->info = [];
$this->warning = [];
$this->error = [];
}
}
HTML 出力仕様
重要度順(error > warning > info)で表示します。
実装:Sample クラスへの組み込み
Composition が正解である理由
メッセージ処理と業務ロジックの責務が完全に分離されるため、後から変更しても影響範囲が最小になります。
Sample の構造
- 値セット(バリデーション)
- 保存処理
- MessageHandler にメッセージを追加
- UI でまとめて表示
Sample(PSR-12 + phpDoc 完全版)
<?php
declare(strict_types=1);
namespace App\Core;
use App\Core\MessageHandler;
/**
* Class Sample
*
* 値のバリデーションと保存処理を行うクラス。
* メッセージは MessageHandler に委譲する。
*/
class Sample
{
/**
* @var int サンプル値
*/
private int $value = 0;
/**
* @var MessageHandler メッセージ管理クラス
*/
private MessageHandler $messages;
/**
* コンストラクタ
*
* @param MessageHandler|null $messages 差し替え可能なメッセージ管理クラス
*/
public function __construct(?MessageHandler $messages = null)
{
$this->messages = $messages ?? new MessageHandler();
}
/**
* MessageHandler インスタンスを返す。
*
* @return MessageHandler
*/
public function messages(): MessageHandler
{
return $this->messages;
}
/**
* 値をセットする。
*
* @param mixed $input 入力値
* @return void
*/
public function setValue(mixed $input): void
{
if ($input === null || $input === '') {
$this->messages->addWarning('値が空だったため 0 を設定しました。');
$this->value = 0;
return;
}
if (is_numeric($input) === false) {
$this->messages->addError('value は数値である必要があります。');
return;
}
$this->value = (int) $input;
$this->messages->addInfo('値をセットしました。');
}
/**
* 値を保存する。
*
* @return void
*/
public function save(): void
{
$success = true; // 想像:実際は保存処理を行う
if ($success === true) {
$this->messages->addInfo('保存が完了しました。');
} else {
$this->messages->addError('保存に失敗しました。');
}
}
}
実行例(整形不要)
$sample = new Sample();
$sample->setValue('abc');
$sample->save();
echo $sample->messages()->renderAllHtml();
注意点
MessageHandler の責務を混ぜない
ロジックや例外、翻訳などを入れると破綻します。メッセージ分類と保持だけに限定してください。
バリデーションとメッセージ処理は完全に別物
MessageHandler が判定ロジックを持つべきではありません。業務クラスが判定し、結果だけ渡します。
HTML とロジックは分離可能
必要なら render 系を別クラスに切り離し、Twig/Blade 側でレンダリングすることも可能です。
DI で差し替え可能にする
テスト用の NullMessageHandler や、JSON 出力用の Handler に差し替えるのも容易です。
分類の境界線を曖昧にしない
info / warning / error の分類ルールは明確にしておく必要があります。曖昧だと UI が崩れます。
まとめ
メッセージ処理は絶対に共通化すべき領域
散らばったメッセージ処理は確実に破綻します。早い段階で MessageHandler を導入したほうが良いです。
Composition が現代 PHP の正解
継承は不要な依存を生み、責務も混在させます。MessageHandler をプロパティとして持つのが最適解です。
3分類 MessageHandler の利点
- 重要度が明確
- UI が組みやすい
- コードの重複がなくなる
- ログ・API への応用が簡単
MessageHandler はプロジェクト全体を整える
導入するだけで、開発効率・品質・保守性が大きく向上します。今日から使うべき仕組みです。