意味例外
「過ちて改めざる、これを過ちという」
—孔子『論語』(紀元前551-479年)
意味のある失敗
汎用例外は何が起きたかを伝えます:
catch (Exception $e) {
echo $e->getMessage(); // "検証に失敗しました"
}
それに対して意味例外はなぜ存在できないかを伝えます:
catch (SemanticVariableException $e) {
foreach ($e->getErrors()->exceptions as $exception) {
echo get_class($exception) . ": " . $exception->getMessage();
// EmptyNameException: 名前は空にできません
// InvalidEmailException: メール形式が無効です
}
}
ドメイン例外クラス
すべての例外はDomainExceptionを継承します。ドメイン層では技術的例外(RuntimeException、InvalidArgumentException等)を使わず、常にドメイン例外を使います。失敗は常にドメインの意味を持つ失敗として表現されます:
abstract class DomainException extends Exception {}
final readonly class EmptyNameException extends DomainException {}
final readonly class InvalidEmailException extends DomainException
{
public function __construct(public string $invalidEmail)
{
parent::__construct("メール形式が無効です: {$invalidEmail}");
}
}
// 年齢関連の存在失敗
abstract class AgeException extends DomainException {}
final readonly class NegativeAgeException extends AgeException {}
final readonly class AgeTooHighException extends AgeException {}
ドメイン例外はメッセージだけでなく構造化データを持ちます。$invalidEmailプロパティから、プログラムは無効なメールアドレスの値にアクセスできます——表示、APIレスポンス、ログなど、さまざまな用途に:
catch (InvalidEmailException $e) {
$logData = [
'invalid_email' => $e->invalidEmail, // プログラムからアクセス可能
'user_ip' => $request->getClientIp(),
'timestamp' => now()
];
Logger::warning('Invalid email attempt', $logData);
}
多言語メッセージ
#[Message]属性で、例外はユーザーの言語で話します:
#[Message([
'en' => 'Name cannot be empty.',
'ja' => '名前は空にできません。',
'es' => 'El nombre no puede estar vacío.'
])]
final readonly class EmptyNameException extends DomainException {}
#[Message([
'en' => 'Age must be at least {min} years.',
'ja' => '年齢は最低{min}歳でなければなりません。'
])]
final readonly class AgeTooYoungException extends DomainException
{
public function __construct(public int $min = 13) {}
}
エラー収集
フレームワークは最初のエラーで止まらず、すべての検証エラーを収集します:
try {
$user = $becoming(new UserInput('', 'invalid-email', 10));
} catch (SemanticVariableException $e) {
// 3つのエラーが同時に収集される:
// - EmptyNameException
// - InvalidEmailException
// - AgeTooYoungException
$messages = $e->getErrors()->getMessages('ja');
// ["名前は空にできません", "メール形式が無効です", "年齢は最低13歳でなければなりません"]
}
最初のエラーで即座に失敗するのではなく、すべての問題を一度に把握できます。
エラーも存在の1つ
エラー状態も変容の有効な結果として扱えます:
#[Be([ValidUser::class, InvalidUser::class])]
final readonly class UserValidation
{
public ValidUser|InvalidUser $being;
public function __construct(#[Input] string $data)
{
try {
$this->being = new ValidUser($data);
} catch (ValidationException $e) {
$this->being = new InvalidUser($e->getErrors());
}
}
}
例外で実行を止めるのではなく、エラーを型として表現する。失敗も成功と同じく、変容の正当な結果です。
フレームワークの全体像はリファレンスへ ➡️