PHP入門:列挙型(Enum)について -vol.5-

スポンサーリンク
PHP入門:列挙型(Enum)について -vol.5- 用語解説
PHP入門:列挙型(Enum)について -vol.5-
この記事は約5分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

今日は、PHPの列挙型(Enum)についてご紹介します。

スポンサーリンク

背景

PHP8.2を利用したAPIを開発しているときに列挙型(Enum)を利用したので、そのときの調査内容を備忘としてのこしました。

こちらのサイトを参考にしています。

列挙型(Enum)拡張できない理由

列挙型(Enum)が拡張できない理由は、列挙型の性質に起因しています。列挙型はメソッドに対する契約を持たないのではなく、ケースに対する契約を持っています。

通常のクラスの継承の例を考えてみましょう:

class A {}
class B extends A {}

function foo(A $a) {}

function bar(B $b) {
    foo($b);
}

このコードは型安全です。なぜなら、BはAの契約に従っており、共変性/反変性の魔法により、メソッドに関する期待が保存されるためです。

しかし、列挙型はケースに対する契約を持ちます。例えば:

enum ErrorCode {
    case SOMETHING_BROKE;
}

function quux(ErrorCode $errorCode)
{
    // このコードはすべてのケースをカバーするように見えます
    match ($errorCode) {
        ErrorCode::SOMETHING_BROKE => true,
    }
}

関数quux内のmatch文は、ErrorCodeのすべてのケースをカバーすることが静的に分析されます。

しかし、列挙型を拡張できる場合を考えてみましょう:

// 列挙型が拡張できる仮想のコード
enum MoreErrorCode extends ErrorCode {
    case PEBKAC;
}

function fot(MoreErrorCode $errorCode) {
    quux($errorCode);
}

fot(MoreErrorCode::PEBKAC);

通常の継承ルールに従うと、別の列挙型が親列挙型を拡張した場合、型チェックは通過するでしょう。

問題は、quux()内のmatch文がすべてのケースをカバーしなくなることです。MoreErrorCode::PEBKACについては知らないため、match文は例外をスローします。

このような理由から、列挙型は拡張できず、finalである必要があります。列挙型はその特定のケースに対して厳格な契約を持つため、拡張可能にすることは型の安全性を損なう可能性があるためです。

列挙型(Enum)サンプル

以下は、PHPの列挙型の使用例です。

例1: 基本的な制限された値

enum SortOrder
{
    case Asc;
    case Desc;
}

function query($fields, $filter, SortOrder $order = SortOrder::Asc) { ... }

この例では、query() 関数は $orderSortOrder::Asc または SortOrder::Desc であることが保証されているため、エラーチェックやテストは不要です。それ以外の値が渡された場合、TypeError が発生するためです。

例2: 発展的な排他的な値

enum UserStatus: string
{
    case Pending = 'P';
    case Active = 'A';
    case Suspended = 'S';
    case CanceledByUser = 'C';

    public function label(): string
    {
        return match($this) {
            static::Pending => 'Pending',
            static::Active => 'Active',
            static::Suspended => 'Suspended',
            static::CanceledByUser => 'Canceled by user',
        };
    }
}

この例では、ユーザーステータスは UserStatus::Pending、UserStatus::Active、UserStatus::Suspended、または UserStatus::CanceledByUser のいずれかで、排他的にそれらの値しか受け入れないことができます。関数はパラメータをUserStatusに対して型指定し、これらの4つの値のみを受け入れます。

これらの4つの値は label() メソッドを持ち、人が読みやすい文字列を返します。この文字列は、データベースフィールドやHTMLセレクトボックスなどで使用できる「マシン名」のスカラー等価文字列とは独立しています。

foreach (UserStatus::cases() as $case) {
    printf('<option value="%s">%s</option>\n', $case->value, $case->label());
}

このコードは、UserStatus::cases() を使用して、すべての列挙型ケースを取得し、それぞれのラベルをHTMLの <option> 要素として出力します。

おわりに

今日は、PHPの列挙型(Enum)についてご紹介しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました