今更ながら PHP の Enum 型を使ってみた

# まえがき

PHP8.1 が出たときは [BenSampo/laravel-enum](https://github.com/BenSampo/laravel-enum) パッケージを利用していて、それで特に困っていなかったため Enum 型については詳しく調べていませんでした

多分僕と同じようにそのままズルズルと時間が過ぎてしまい、未だに Enum 型を使ったことない方は結構いらっしゃると思うので、そういう方のために使ってみたメモを残します

# Enum

Enumerations (列挙型) とは、クラスのようなもので、特定かつ同一の型のスカラー値のみを持つことができる型を定義します

## 設定可能なスカラー値

- int
- string

## 利用可能なマジックメソッド

- __call()
- __callStatic()
- __invoke()

## サンプルコード

int 型の公開フラグ
**（※Enum 型はインスタンスメソッド、静的メソッドどちらも定義可能です）**

```php
<?php
namespace App\Enums;

enum ViewFlg: int
{
    /**
     * チェック待ち
     *
     * @var int
     */
    case CHECK = 3;

    /**
     * 準備中
     *
     * @var int
     */
    case PREP = 2; 

    /**
     * 公開
     *
     * @var int
     */
    case SHOW = 1;

    /**
     * 非公開
     *
     * @var int
     */
    case HIDE = 0;

    /**
     * @return string
     */
    public function description(): string
    {
        return match ($this) {
            self::CHECK => 'チェック待ち',
            self::PREP  => '準備中',
            self::SHOW  => '公開',
            self::HIDE  => '非公開',
        };
    }

    /**
     * 公開中か判定
     *
     * @return bool
     */
    public function isShow(): bool
    {
        return (
            $this === self::SHOW
        );
    }

    /**
     * 存在チェック
     *
     * @static
     * @return bool
     */
    public static function has($value): bool
    {
        return (
            !is_null(self::tryFrom($value)
        );        
    }
}
```

なんと Trait も使えます！（サンプルコードでは使ってないですが）
定数も設定できます！（サンプルコードでは（ｒｙ



### 注意事項

異なる型のスカラー値を同一の Enum に設定することはできません。null も駄目です。
弊社では**削除したレコードに view_flg = null を格納していた**ので、やっちまったなと思いました

#### 駄目な例

```php
// ? で表現するのはNG
enum ViewFlg: ?int
{
    case SHOW = 1;
    // int 型でないのでNG
    case DELETE = null;
}
```

#### 何とか null を設定したい場合の抜け道を考える

Enum 型ではなく、プロパティに ViewFlg 型と null を許可すればいいかも知れません
ただ「削除」という description を持てないのでこれでも困るのですが…

```php
class Commodity
{
    /**
     * @var ViewFlg|null $viewFlg
     */
    private readonly ?ViewFlg $viewFlg;

    /**
     * @param  ViewFlg|null $viewFlg
     */
    public function __construct(?ViewFlg $viewFlg)
    {
        $this->viewFlg = $viewFlg;
    }
}
```

まぁそういう場合は [BenSampo/laravel-enum](https://github.com/BenSampo/laravel-enum) パッケージを使えばいいですね！！



## Enum 型がデフォルトで持つ静的メソッド

### from()

与えられたスカラー値を使用して、対応する列挙型のインスタンスを返却します
スカラー値が Enum 内に存在しない場合、例外が発生します

```php
// ViewFlg::SHOW インスタンス
$viewFlg = ViewFlg::from(1);
// 例外が発生
$viewFlg = ViewFlg::from(5);
```

### tryFrom()

与えられたスカラー値を使用して、対応する列挙型のインスタンスを返却します
存在しない場合、null を返却します

存在しない場合はデフォルト値を入れたいっていう用途とかでは使えそうです

```php
// ViewFlg::SHOW インスタンス
$viewFlg = ViewFlg::from(1);
// null
$viewFlg = ViewFlg::from(5);

// デフォルト値として ViewFlg::SHOW を代入
$viewFlg = ViewFlg::from(5) ?? ViewFlg::SHOW;
```


### case()

列挙型のすべてのケースを返却します

用途が難しいです…

```php
// [
//    0 => ViewFlg::CHECK,
//    1 => ViewFlg::PREP,
//    2 => ViewFlg::SHOW,
//    3 => ViewFlg::HIDE,
// ]
$viewFlgs = ViewFlg::case();
```

### インスタンス化後

当然インスタンスメソッドが使えます
name, value プロパティにもアクセスできます

```php
$viewFlg = ViewFlg::from(1);

// 公開中
$viewFlg->description();
// true
$viewFlg->isShow();

// "SHOW"
$viewFlg->name;
// 1
$viewFlg->value;
```

# あとがき

Laravel Model の casts プロパティにも設定出来るっぽいのでいいかも知れません

```php
protected $casts = [
    'view_flg' => ViewFlg::class,
];

// なんで Enum 型なのに ::class が使えるんや？と対面で聞かれたのでここに書いておきますが
// ::class は「名前空間の完全修飾名」を取得するものなので
// 別に class とか Enum とか interface とか abstract とか関係ないのです
```



ただ今回使ってみて [BenSampo/laravel-enum](https://github.com/BenSampo/laravel-enum) から乗り換えたくなる強い理由が見つかりませんでしたね…誰か教えてください

# 参考

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