株式会社クイックのWebサービス開発blog

HAPPYなエンジニア&デザイナーのブログです

Enum を利用したステータスの条件分岐

南国育ちなんですが、寒いの結構好きです。
フルーツパーラーです。

最近の興味はドメイン駆動設計をどうやってプログラムに落としていくかです。

現場で役立つシステム設計の原則」がとても面白かったので、
その中のコードをPHPでアレンジしてお伝えしようと思います。

細かく知りたい方は是非読んでみて下さい。

こんな方に読んで欲しい

  • PHP で実装したい
  • Enum を使いステータス判定処理を実装したい
  • 気がついたらステータス判定のロジックがあらゆる処理に染み出している

お伝えしたい事

  • ステータスを列挙型で定義しておくと、入力判定、ステータスの変更ルールをまとめやすい
  • 列挙型として定義しておくことで、コード補完の恩恵を最大限に受けられる(これは思ってた以上に便利)

サンプルの説明

採用フローをイメージしたステータス変更ロジックで考えてみます。
ステータスは、エントリー > 選考 > 適正試験 > 面接 > 内定 の順に進み、
次のステップにしか進む事が出来ない仕様です。

ステータスの変更可能パターン

from / to 選考 適正試験 面接 内定
エントリー
選考
適正試験
面接

実装時に考えるvalidation

  • 存在するステータスかどうか
  • 遷移して良いステータスかどうか

Enum(列挙型)を利用し実装してみます

PHPは標準でEnumをサポートしていません。
※ リフレクションを使い実装している方がいましたので、参照させて頂きました。
※ 以下のコードは参照リンクEnum クラスを継承する想定です。

選考フローを定義

<?php

/**
 * 参照の Enum クラスを継承
 * 列挙型の選考フロー
 * Class EntryStatus
 */
final class EntryStatus extends Enum
{
    const ENTRY = 1;
    const SENKOU = 2;
    const TEKISEI = 3;
    const MENSETSU = 4;
    const NAITEI = 5;
}

遷移可能か判定するロジック

canTransition メソッドの中で、次のステップに遷移可能かどうかを判定しています。
判定ロジックがアプリの至る所で発生すると、保守が非常に難しく、
ビジネスロジックの中が if 文で溢れかえります。
Transition クラスの中に閉じ込めています。

<?php

/**
 * 可能な範囲の選考フロー遷移かどうかを判断するクラス
 * Class Transition
 */
class Transition
{
    protected $before;
    protected $after;

    /**
     * Transition constructor.
     * @param EntryStatus $before
     * @param EntryStatus $after
     */
    public function __construct(EntryStatus $before, EntryStatus $after)
    {
        $this->before = $before->valueOf();
        $this->after = $after->valueOf();
    }

    /**
     * 遷移可能か
     * @return bool
     */
    public function canTransition()
    {
        $pattern = array();

        // ここで遷移出来る全てのパターンの確認
        if ($this->before === EntryStatus::ENTRY) {

            $pattern = array(EntryStatus::SENKOU); // 遷移パターンが複数あれば、配列に追加

        } elseif ($this->before === EntryStatus::SENKOU) {

            $pattern = array(EntryStatus::TEKISEI);

        } elseif ($this->before === EntryStatus::TEKISEI) {

            $pattern = array(EntryStatus::MENSETSU);

        } elseif ($this->before === EntryStatus::MENSETSU) {

            $pattern = array(EntryStatus::NAITEI);

        }

        return in_array($this->after, $pattern, true);
    }

}

学生の選考情報(ステータスを)を保持するクラス

canTransition メソッドをコールし、遷移可能なステップか判定。
遷移不可な場合はメッセージを表示するような処理が続きます。
遷移可能な場合はステータスを登録。

<?php

/**
 * 学生の選考情報
 * Class RecruitStep
 */
class RecruitStep
{

    /**
     * @return string
     */
    private function getStatus()
    {
        // 実際は DB から取得する or 既に DB から取得済
        // Repository(永続化)層で行う
        return EntryStatus::ENTRY;
    }

    /**
     * 選考フローの登録
     * @param EntryStatus $entry
     */
    public function save(EntryStatus $entry)
    {
        // 現在のステータスを取得
        $status = $this->getStatus();
        $before = new EntryStatus($status);

        // ステータス変更が可能か判断
        $transition = new Transition($before, $entry);
        $available = $transition->canTransition();

        if ($available) {
            // DB にステータスを登録
            // ...
        }

        // メッセージ表示の後続処理
        // ...

    }
}

ステータスを登録する場合は、引数に Enum の値を注入します

<?php
$step = new RecruitStep();
$step->save(new EntryStatus(EntryStatus::SENKOU));

まとめ

Transition クラスの中で遷移可能なステータスかを判定し、処理を集約出来ます。
入力値に Enum を利用する事で、存在しないステータスが入る事を防ぐ事が可能です。

ゆるふわPHPを利用する上で、さくっと書く場面、少し堅く場面を意識しながら書いていきたいですね。