PHP Enumについて

# 概要

- PHPのEnumについてちゃんと理解してまとめる

# Enumって？

- 取りうる値を限定した独自の型を定義できるクラスに似ているもの。
- 似ているだけでクラスではない。クラスやオブジェクトの上に構築されている。

# Enumのルール

- コンストラクタ、デストラクタは書けない。
- 継承をすることも継承されることもできない。
- プロパティーは許可されていない。
- caseをcloneすることはサポートされていない。
- 使う前に宣言しないといけない。
- newを使って直接インスタンス化できない。
- `__call`と`__callStatic`と`__invoke`以外のマジックメソッドは許可されていない。
- `__CLASS__`と`__FUNCTION__`以外のマジック定数は許可されていない。
- インターフェースの実装可能
- アトリビュートを深する事が可能
- 定義可能メソッド
    - public
    - private
    - protected
- 定義可能staticメソッド
    - public
    - private
    - protected

# Enumの種類

### PureEnum

- PureCaseだけを含むEnumのこと。
- PureCase
    - 関連するデータが無い名前だけのCase
- 例

    ```php
    <?php
    enum Suit
    {
        case Hearts;
        case Diamonds;
        case Clubs;
        case Spades;
    }
    ```

- 読み取り専用プロパティーのnameがある。下記にようにEnumを呼び出すことでcaseで定義されているcaseそのものの名前を得ることができる。

    ```php
    <?php
    // Heartsが格納される
    $suit = Suit::Hearts->name;
    ```

### BackedEnum

- スカラー値を持つCaseが含まれるEnumのこと。
- スカラー値
    - スカラー型の値のこと
    - PHPだと論理型（bool）、整数型（int）、浮動小数点型（float, double, real）、文字列型（string）がスカラー型
- 例

    ```php
    <?php
    enum Suit: string
    {
        case Hearts = 'H';
        case Diamonds = 'D';
        case Clubs = 'C';
        case Spades = 'S';
    }
    ```

- 一つのEnumで持てるスカラー値の型は一つだけ。下記みたいな書き方はできない。（そもそも`enum Suit: string|int`という書き方ができない。仮に　`enum Suit: string`でスカラー値がstringとintが入り混じっていたら「Enum定義時のstring型とマッチしない物があるエラー」が出る。）

    ```php
    <?php
    enum Suit: string|int
    {
        case Hearts = 'H';
        case Diamonds = 1;
        case Clubs = 'C';
        case Spades = 2;
    }
    ```

- スカラー値はユニークである必要がある。下記みたいな書き方はできない。

    ```php
    <?php
    enum Suit: string
    {
        case Hearts = 'H';
        case Diamonds = 'H';
        case Clubs = 'H';
        case Spades = 'H';
    }
    ```

- 読み取り専用プロパティーのvalueがある。下記のように記載することでcaseの名前に紐づくスカラー値を取得する事ができる。

    ```php
    <?php
    // Hが格納される
    $suitValue = Suit::Hearts->value;
    ```

- BackedEnumはBackedEnumインターフェースを実装しているらしい。そのためfromとtryFromという2個のstaticな関数を呼び出す事ができる。
- from関数を使って値からEnumのインスタンスを取得する。
    - 下記のようにfrom関数を用いることで引数の値とcaseに紐づくスカラー値を比較、一致するものがあれば当該caseのオブジェクトを返す。（引数とスカラー値の比較はPHP側の設定で「強い比較」「弱い比較」を選択できる。強い比較 → [PHP declare(strict_types=1)ってなんだ！？](https://qiita.com/miriwo/items/8d8ef98784ccfc4297d9)）

        ```php
        <?php
        
        enum Suit: string
        {
            case Hearts = 'H';
            case Diamonds = 'D';
            case Clubs = 'C';
            case Spades = 'S';
        }
        
        $suit = Suit::from('H');
        
        // enum(Suit::Hearts)と出力される
        var_dump($suit);
        ```

    - 引数の値がcaseに紐づくスカラー値のいずれとも一致しなかった場合エラーを返す。

        ```php
        <?php
        
        enum Suit: string
        {
            case Hearts = 'H';
            case Diamonds = 'D';
            case Clubs = 'C';
            case Spades = 'S';
        }
        
        // エラー発生　「PHP Fatal error:  Uncaught ValueError: "X" is not a valid backing value for enum "Suit"」
        $suit = Suit::from('X');
        ```

- tryFrom関数を用いて値からEnumのインスタンスを取得する。 
    - 下記のようにtryFrom関数を用いることでfrom関数の時と同様の振る舞いをする。

        ```php
        <?php
        
        enum Suit: string
        {
            case Hearts = 'H';
            case Diamonds = 'D';
            case Clubs = 'C';
            case Spades = 'S';
        }
        
        $suit = Suit::tryFrom('H');
        
        // enum(Suit::Hearts)と出力される
        var_dump($suit);
        ```

    - 引数の値がcaseに紐づくスカラー値のいずれとも一致しなかった場合nullを返す。 

        ```php
        <?php
        
        enum Suit: string
        {
            case Hearts = 'H';
            case Diamonds = 'D';
            case Clubs = 'C';
            case Spades = 'S';
        }
        
        $suit = Suit::tryFrom('X');
        
        // nullと出力される
        var_dump($suit);
        ```

# 関数の定義

- PureEnum、BackedEnum共に関数を定義することは可能である。（public, private, protectedの関数が定義できるけど、結局Enumは継承できないから何を指定してもprotectedと実質は同じ）
- 例

    ```php
    <?php
    
    enum Suit: string
    {
        case Hearts = 'H';
        case Diamonds = 'D';
        case Clubs = 'C';
        case Spades = 'S';
        
        public function foo(){
            return 'トランプは楽しい';
        }
    }
    
    $suit = Suit::Hearts->foo();
    
    // 「トランプは楽しい」と出力される
    echo $suit;
    ```

- トレイトをuseすることもできる。

    ```php
    <?php
    
    trait Values{
        public function foo(){
            return 'トランプは楽しい';
        }
    }
    
    enum Suit: string
    {
        use Values;
        
        case Hearts = 'H';
        case Diamonds = 'D';
        case Clubs = 'C';
        case Spades = 'S';
    }
    
    $suit = Suit::Hearts->foo();
    
    // 「トランプは楽しい」と出力される
    echo $suit;
    ```

- 下記のように記載すればPureEnumでも紐づく値を設定する事ができる。

    ```php
    <?php
    
    enum Suit
    {
        case Hearts;
        case Diamonds;
        case Clubs;
        case Spades;
        
        public function values(){
            return match ($this) {
                Suit::Hearts => 'H',
                Suit::Diamonds => 'D',
                Suit::Clubs => 'C',
                Suit::Spades => 'S',
            };
        }
    }
    
    // 「H」と出力される
    echo Suit::Hearts->values();
    ```

- コンストラクタの代わりをさせたいならstatic関数を定義する。

    ```php
    <?php
    
    enum Suit: string
    {
        case Hearts = 'H';
        case Diamonds = 'D';
        case Clubs = 'C';
        case Spades = 'S';
        
        public static function foo(int $int){
            return match ($int) {
                1 => Suit::Hearts,
                2 => Suit::Diamonds,
                3 => Suit::Clubs,
                4 => Suit::Spades,
            };
        }
    }
    
    $suit = Suit::foo(1);
    
    // 「enum(Suit::Hearts)」と出力される
    var_dump($suit);
    ```

# Enumのcaseを配列で返す

- 下記のように記載することでEnumのcaseを宣言された順番で配列で取得する事ができる。

    ```php
    <?php
    
    enum Suit: string
    {
        case Hearts = 'H';
        case Diamonds = 'D';
        case Clubs = 'C';
        case Spades = 'S';
    }
    
    var_dump(Suit::cases());
    ```

# 参考文献

- [https://www.php.net/manual/ja/language.enumerations.php](https://www.php.net/manual/ja/language.enumerations.php)