🦍

PHPに詳しいアンミカ「Enumって2種類あんねん」

2023/12/21に公開

この記事は Laravel Advent Calendar 2023 21日目の記事です。

疑問

caseに対応する値を定義する方法は2種類があるが、どちらで記述するのが良いのか?

① BackedEnum

enum SIGNAL:string{
    case RED='赤';
    case YELLOW='黄';
    case BLUE='青';
}

② PureEnum

enum SIGNAL{
    case RED;
    case YELLOW;
    case BLUE;

    public function label():string{
        return match($this){
            self::RED=>'赤',
            self::YELLOW=>'黄',
            self::BLUE=>'青',
        };
    }
}

PureEnumとBackedEnum

上記の①のように、caseの右辺に対応する値(※以後、スカラー値と呼ぶ)を定義しているEnumをBackedEnumと言う。一方、②のようにスカラー値を定義していないEnumをPure Enumという。

PureEnumとBackedEnumの大きな違いは、BackedEnumインターフェースを実装しているか否かであり、BackedEnumはそれを実装しています。なのでBackedEnumインターフェースについて考えることで、どのような意図でスカラー値を定義すべきかを理解出来ると思います。

interface BackedEnum{
public static from(int|string $value): static
public static tryFrom(int|string $value): ?static
}

BackedEnumの使い所

fromとtryFromはスカラー値からEnumを生成するためのメソッドです。スカラー値に対応するcaseが存在しない場合に、fromではErrorが生じますが、tryFromではnullを返します。

SIGNAL::tryFrom('赤')  //SIGNAL::RED
SIGNAL::from('赤')  //SIGNAL::RED

SIGNAL::tryFrom('黒')  //null
SIGNAL::from('黒') //Error

上記メソッドから、BackedEnumはデータベースに保存しているマスタやユーザー入力値から対応するEnumを生成するために用意されていると思いました。
なので、Enumの値をユーザーへの表示のために変換したい場合はmatchを用いたメソッドを定義する。DBやユーザー入力値からのEnumへの変換のように、アプリケーション外部からのマッピングが必要な際は、スカラー値の定義を用いるのが良いと思いました。

ユーザー表示への変換とアプリケーション外部からのマッピングの併用

redやyellowという文字列をマスタとしてDBで管理している場合はスカラー値として定義し、ユーザー表示のための変換はメソッドで定義します。
こうすることで、ユーザー表示向けの変換を複数定義することが可能になります。

enum SIGNAL:string{
    case RED="red";
    case YELLOW="yellow";
    case BLUE="blue";

    public function label():string{
        return match($this){
            self::RED=>'赤',
            self::YELLOW=>'黄',
            self::BLUE=>'青',
        };
    }

    public function label2():string{
        return match($this){
            self::RED=>'止まれ!',
            self::YELLOW=>'気をつけて渡れ!',
            self::BLUE=>'渡れ!',
        };
    }
}

Enumの基本的な使い方まとめ

1. name,valueプロパティ
nameでcaseに定義した値にアクセスできるので、単に各enumに対応する文字列にアクセスしたいだけなら、スカラー値を定義する必要はない。

enum SIGNAL: string
{
    case RED = "red";
    case YELLOW = "yellow";
    case BLUE = "blue";
} 

$name=SIGNAL::RED->name; // RED
$value=SIGNAL::RED->value; // red

2. そのまま比較可能
enumはシングルトンなので、objectのまま比較可能です。nameやvalueプロパティを取り出して比較する必要はない。

$red1 = SIGNAL::RED;
$red2 = SIGNAL::RED;

echo $red1 === $red2; // true

3. Eloquentのcastsプロパティに指定可能

protected $casts = [
    'color' => SIGNAL::class
];

https://readouble.com/laravel/10.x/ja/eloquent-mutators.html#enum-casting

まとめ

BackedEnumは、DBやユーザー入力などのアプリケーション外部の値からenumへ変換するために存在していると思います。よって、単にアプリケーション内部だけで用いるenumにはPureEnumを選択するのが良いと思いました。
こうすることで、そのenumが外部の値に依存して(Backed)いるのか否かが明確になります。依存している場合としていない場合では、enumの値を書き換える際に考慮すべき範囲が異なってくるので、メンテナンスしやすくなると思いました。

https://www.php.net/manual/ja/language.enumerations.backed.php

マナリンク Tech Blog

Discussion