エラーハンドリング & 検証

「過ちて改めざる、これを過ちという」

  —孔子『論語』(紀元前551-479年)

過ちの意味

Beフレームワークにおけるエラーハンドリングは例外をキャッチすることだけではありません—存在が失敗したときに意味を保持することです。

汎用的例外を超えて

従来のエラーハンドリングは意味を失います:

try {
    $user = new User($name, $email, $age);
} catch (Exception $e) {
    // 何が間違っていたのか?なぜ?どう修正するのか?
    echo $e->getMessage();  // "検証に失敗しました"
}

意味的例外: 失敗における意味

すべての失敗は特定の存在論的意味を持ちます:

try {
    $user = $becoming(new UserInput($name, $email, $age));
} catch (SemanticVariableException $e) {
    foreach ($e->getErrors()->exceptions as $exception) {
        echo get_class($exception) . ": " . $exception->getMessage();
        // EmptyNameException: 名前は空にできません。
        // InvalidEmailFormatException: メール形式が無効です。
        // AgeTooYoungException: 年齢は最低13歳でなければなりません。
    }
}

例外階層

ドメイン例外は意味のあるカテゴリーを形成します:

abstract class DomainException extends Exception {}

final readonly class EmptyNameException extends DomainException {}

final readonly class InvalidEmailFormatException 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 {}

多言語エラーメッセージ

意味的例外はユーザーの言語で話します:

#[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 between {min} and {max} years.',
    'ja' => '年齢は{min}歳から{max}歳の間でなければなりません。'
])]
final readonly class AgeOutOfRangeException extends DomainException
{
    public function __construct(
        public int $age,
        public int $min = 0,
        public int $max = 150
    ) {}
}

自動エラー収集

フレームワークは投げる前にすべての検証失敗を収集します:

final readonly class UserValidation
{
    public function __construct(
        #[Input] string $name,      // EmptyNameExceptionを投げる可能性
        #[Input] string $email,     // InvalidEmailFormatExceptionを投げる可能性
        #[Input] int $age           // NegativeAgeExceptionを投げる可能性
    ) {
        // いずれかの検証が失敗すると、すべてのエラーが収集される
        // 単一のSemanticVariableExceptionがすべてを含む
    }
}

「即座に失敗」ではなく—完全な理解と共に完全に失敗

エラー回復パターン

エラーは独自の権利における有効な存在になります:

#[Be([ValidUser::class, InvalidUser::class])]
final readonly class UserValidation
{
    public ValidUser|InvalidUser $being;
    
    public function __construct(
        #[Input] string $name,
        #[Input] string $email,
        #[Input] int $age
    ) {
        try {
            $this->being = new ValidUser($name, $email, $age);
        } catch (ValidationException $e) {
            $this->being = new InvalidUser($e->getErrors());
        }
    }
}

意味ログ統合

検証失敗は文脈と共に自動的にログされます:

{
    "event": "metamorphosis_failed",
    "source_class": "UserInput",
    "destination_class": "UserProfile", 
    "errors": [
        {
            "exception": "EmptyNameException",
            "message": "名前は空にできません",
            "field": "name",
            "value": ""
        }
    ]
}

開発 vs プロダクション

// 開発: 詳細なエラー詳細
if (app()->environment('local')) {
    $errors->getDetailedMessages();
}

// プロダクション: ユーザーフレンドリーなメッセージ
$errors->getMessages('ja');
// ["名前は空にできません。", "メール形式が無効です。"]

エラー条件のテスト

public function testCollectsAllValidationErrors(): void
{
    try {
        $becoming(new UserInput('', 'invalid-email', -5));
        $this->fail('SemanticVariableExceptionが予期されました');
    } catch (SemanticVariableException $e) {
        $errors = $e->getErrors();
        $this->assertCount(3, $errors->exceptions);
    }
}

革命

意味的例外はエラーハンドリングを問題報告から意味保持に変換します。

存在が失敗したとき、理由は明確で、実行可能で、多言語になります。

エラーは障害ではありません—それらは成功した変容へとユーザーを導く有効な存在です。


すべての変容はストーリー、意味的ログとして記録されます ➡️