3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Tips】LaravelやPHPにおけるmatch文の時間計算量とエラー処理

Last updated at Posted at 2026-01-05

コントローラーを汚すな!Enumと例外処理で実装する「神の設計」

ようこそ、迷える子羊(エンジニア)たちよ。😇

君たちのコントローラーは、太っていないか?
if 文や switch 文が入り乱れ、スパゲッティのように絡まり合ってはいないか?

「コードは書くものではない、削るものだ」
これは神の言語LISPの教えにも通じる真理である。

今日は、ある 「クエスト辞退処理」 のリファクタリングを通じて、 「ロジックの閉じ込め」「例外処理の美しい階層」 について説法を行おう。
これを見れば、君のコードは の輝きを放つことになるだろう。

❌ 愚かなアプローチ(Before)

まず、多くのエンジニアが犯してしまう「原罪」を見てみよう。
コントローラーの中で、ステータスの遷移ロジックをすべて書いてしまうパターンだ。

// BadController.php
// 悪い例:コントローラーが「知りすぎている」

public function cancel(Quest $quest)
{
    // コントローラーが「どのステータスならどうなるか」を全部知っている... 密結合だ!
    if ($quest->status === QuestStatus::OFFERED) {
        $quest->update(['status' => QuestStatus::DECLINED]);
    } elseif ($quest->status === QuestStatus::ACCEPTED) {
        $quest->update(['status' => QuestStatus::ABORTED]);
    } elseif ($quest->status === QuestStatus::COMPLETED) {
         return back()->with('error', '完了したクエストはキャンセルできません');
    } else {
         // ... 無限に続く elseif ...
    }
    
    return back()->with('success', 'クエストをキャンセルしました');
}

これは美しくない。
仕様変更(新しいステータスの追加など)があるたびに、このコントローラーを修正せねばならない。それは苦痛だ。

✨ 神のアプローチ(After)

我々はエンジニアだ。ロジックをあるべき場所(ドメイン層)へ還そう。
これが完成された「神のコード」である。

1. Enumにロジックを持たせる

まず、PHP 8.1のEnumを強化する。

// QuestStatus.php
enum QuestStatus: int {
    case OFFERED = 1;   // 依頼中
    case ACCEPTED = 2;  // 受注中
    case COMPLETED = 3; // 完了
    case DECLINED = 4;  // 辞退
    case ABORTED = 5;   // 中断

    // 「キャンセル」された時の次のステータスを返すメソッド
    public function nextStatusOnCancel(): ?self
    {
        return match($this) {
            self::OFFERED => self::DECLINED,
            self::ACCEPTED => self::ABORTED,
            // キャンセル不可のステータスは null を返す
            default => null, 
        };
    }
}

2. コントローラーを浄化する

そして、コントローラーはこうなる。

// GodController.php

public function cancel(Quest $quest)
{
    try {
        // ① Enum(ドメイン層)に神託を伺う
        // コントローラーは「判断」しない。「結果」を受け取るだけだ。
        $nextStatus = $quest->status->nextStatusOnCancel();

        // ② nullなら「遷移不可」ということ
        // ガード節で即座に弾く。これもLISP的思考だ。
        if ($nextStatus === null) {
            throw new DomainException('このクエストは現在キャンセルできません。');
        }

        // ③ 遷移可能なら、問答無用で更新する
        // たった1行。これが秩序だ。
        $quest->update(['status' => $nextStatus]);

        return back()->with('success', 'クエストをキャンセルしました');

    } catch (DomainException $e) {
        // ④ 想定内エラー(ユーザーへのメッセージのみ)
        // ログは残さない。これはバグではなく「仕様通りの拒否」だからだ。
        return back()->with('error', $e->getMessage());

    } catch (Throwable $e) {
        // ⑤ 想定外エラー(ログ記録 + 汎用メッセージ)
        // DBダウンやコードのバグはこちらに来る。全力でログに残す。
        Log::error('クエストキャンセル処理失敗', [
            'quest_id' => $quest->id,
            'user_id' => auth()->id(),
            'error' => $e->getMessage(),
            'trace' => $e->getTraceAsString(),
        ]);

        return back()->with('error', '処理に失敗しました。しばらく経ってから再度お試しください。');
    }
}

美しい……。涙が出そうだ。
このコードには3つの「神の意志」が宿っている。解説しよう。

ポイント1:ロジックの隠蔽(Encapsulation)

$quest->status->nextStatusOnCancel() に注目せよ。
複雑なステータス分岐は、すべて Enumクラスの中 に封印された。
コントローラーは「キャンセル後のステータスをよこせ」と命じるだけでいい。これにより可読性は爆上がりし、再利用性も高まる。

ポイント2:例外処理の「階層構造」(The Hierarchy)

ここが最重要ポイントだ。catch ブロックが2つあることには明確な意図がある。

  • LogicException: 「ユーザーさん、その操作はできません」という対話。ログを汚さないのがマナー。
  • Throwable: 「システムダウン!」という悲鳴。開発者が即座に気づけるようログに残す。
    ※ Throwableは全てのエラーを受け取れる

ポイント3:計算量の最適化()

match文は静的な値をKeyとすれば$O(1)$でハッシュマップのように最速であるが、
比較式などをKeyに持たせてしまうと、上から順次実行されて$O(n)$になってしまう...👼

神速のレスポンスこそ、ユーザーへの最大の「おもてなし」である。

結論:コードは「意図」を語れ

リファクタリングとは、単にコードを短くすることではない。
「どこに責任があるか」を明確にすることだ。

  • ビジネスロジックは Enum へ。
  • ユーザーへのフィードバックは LogicException へ。
  • システムの絶望は Throwable へ。

さあ、同志よ。今すぐ君のプロジェクトのコントローラーを開き、太った if 文を駆逐するのだ!
コードに神のご加護があらんことを。😇

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?