LaravelでDBの区分値をEnum化していい感じに扱う

ソフトウェアでテーブル内の区分値を扱う方法は様々あるかと思いますが、個人的に扱いやすくできたので備忘録です。
本稿における区分値とは以下のような各種区分をDB上で扱いやすくするために、数値化などをおこなった値を指しています。

```php:性別区分
[
    'gender' => [
        0 => 'not known',
        1 => 'male',
        2 => 'female',
        9 => 'not applicable'
    ]
]
```

```php:権限区分
[
    'roles' => [
        1 => 'developer',
        2 => 'administrator',
        3 => 'staff'
    ]
]
```

# TL;DR
[myclabs/php-enum](https://github.com/myclabs/php-enum) を使用して、区分値をEnum化し、Model内でよしなにする。

# Enumって何
Enumとは `Enumerate` の略で `列挙` を意味します。列挙型は、その名の通り定数を列挙したリストのようなものを作成することができ、区分値を扱う際によく利用されます。実装にもよりますが、値が同じでも異なるオブジェクトとして扱うことができるというメリットがあります。

例えば、前述の性別と権限の区分値を定数として実装した場合 `MALE === DEVELOPER` はどちらも値が `1` であるため `true` となり、バグを生み出してしまう可能性があります。

Enumは、言語によっては組み込まれている場合もありますが、PHPには組み込まれていない型であるため、PHPで使用したい場合は、自作するか外部ライブラリを使用する必要があります。

一般的に定数を扱うよりも処理が遅くなるので、定数はすべて列挙型というわけではなく、区分値などの適した場面で使用するのがよいかと思います。

# 区分値をEnum化する
## Enumライブラリのインストール
PHPのEnumライブラリは複数ありますが、本稿ではGitHubのStar数が多い & 汎用的に扱えることから、[myclabs/php-enum](https://github.com/myclabs/php-enum)を使用します。

以下のコマンドでインストールします。

```bash
$ composer require myclabs/php-enum
```

## Enumの作成
ファイルを作成する場所はお好みですが、今回は `app` 以下に `Enums` ディレクトリを作成し、そこに定義します。権限区分を例にEnumを作っていきます。

```php:Role.php
<?php

namespace App\Enums;

use MyCLabs\Enum\Enum;

class Role extends Enum
{
    const DEVELOPER = 1;
    const ADMINISTRATOR = 2;
    const STAFF = 3;
}

```

Enumの作成はこれだけです。

# モデルからRoleのEnum値を取得できるようにする
`User` のModel内に `User` の `role_code` 属性のEnum値を取得するアクセサを定義します(LaravelのEloquentでは `getHogeHogeAttribute` などのメソッドを定義することで、 `$user->hoge_hoge` のように取得することができるようになります)。

まず、 `User` に先程作成したEnum `Role` をuseします。

今回採用したEnumライブラリはコンストラクタにEnumに設定した値を渡すことで、定義した値を持つEnum値を得ることができます。以下のように記述し、`$user->role` で `$user->role_code` に対応するEnum値が取得できるようします。
(モデルのソースはuseなど色々省略しています)

```php:User.php
<?php

namespace App\Models;

use App\Enums\Role;

class User
{
    public function getRoleAttribute()
    {
        return new Role($this->role_code);
    }

}

```

# Enumを使ってモデルに区分値を設定できるようにする
Userにミューテターを定義し、Userモデルの `role` 属性に `Role` のEnum値を代入することで、同時に `role_code` にも対応する区分値が代入されるようにします。

```php:setRoleAttribute
public function setRoleAttribute(Role $role)
{
    $this->role_code = $role->getValue();
}
```


# Modelから権限を判定できるようにする
せっかくなので、 `php-enum` に備わっている `equals` を利用してModel内で権限も判定できるようにしておきます。

equalsに渡す引数はメソッド呼び出しの形式であることに注意します。 `()` をつけないとクラス変数という扱いになり、正しい比較ができません。 メソッド呼び出しの形式にすることで、 Enum型を返却してくれます。

```php:User.php
<?php

namespace App\Models;

use App\Enums\Role;

class User
{
    public function getRoleAttribute()
    {
        return new Role($this->role_code);
    }

    public function setRoleAttribute(Role $role)
    {
        $this->role_code = $role->getValue();
    }

    public function getIsDeveloperAttribute()
    {
        return $this->role->equals(Role::Developer());
    }

    public function getIsAdministratorAttribute()
    {
        return $this->role->equals(Role::Administrator());
    }

    public function getIsStaffAttribute()
    {
        return $this->role->equals(Role::STAFF());
    }

}

```

これで権限を判定する場面で扱いやすくなりました。

# Labelが欲しい
それぞれの権限名称をフロント側に表示したい場合などがあるかと思います。Laravelの機能を使ってスマートに取得できるようにします。

## langファイルを作成する
Laravelには `app/resources/lang` 以下に配置したディレクトリから、渡されたキーに対応する文字列を返却してくれる `__` というヘルパーメソッドがいます。 `app/resources/lang` 以下には locale名を関したディレクトリが存在し、configで設定されたlocaleと同じ名前を持つディレクトリから取得される仕組みになっています。

今回はlocaleを `ja` に設定していますので、`app/resources/lang/ja` 以下に `my_app.php` を作成し、各区分の名称を記述します(ファイル名はなんでもいいです)。

```php:my_app.php
<?php

return [
    'developer'     => '開発者',
    'administrator' => '管理者',
    'staff'         => '担当者'
];

```

これでコード内で `__('myapp.developer')` といったようにコールすることで、 `'開発者'` が取得できます。また、言語別にファイルを用意すればそれぞれの言語で取得できます。

## Enum内に `getLabel` メソッドを作成する
メソッド名は何でもいいです。マジックメソッド `__toString` をオーバーライドしても良いのですが、デフォルトの挙動を破壊するのがなんとなく怖いので、今回は新たにメソッドを定義します。

`getLabel` では lang に定義した、Enumに対応する文字列を取得できるようなロジックを記述します。
今回はEnumのKeyを小文字にするだけで、langに定義したKeyになることと、今後 `Role` が追加されたとしても同じように対応できるだろうという考えで、EnumインスタンスのKeyを`strtolower`で小文字にすることでlangのキーを生成しています。単純に小文字にするだけでは対応できない場合は `Enumのキー => langのキー` のような連想配列を作成して取得するのがよさそうですね。

```php:Role.php
<?php

namespace App\Enums;

use MyCLabs\Enum\Enum;

class Role extends Enum
{
    const DEVELOPER = 1;
    const ADMINISTRATOR = 2;
    const STAFF = 3;

    public function getLabel() {
        return __('my_app.'.strtolower($this->getKey()));
    }
}

```

## Model内に `getRoleLabelAttribute` メソッドを作成する
最後に以下のようなメソッドをモデル内に定義すれば、 `$user->role_label` のような形でLabelを取得できます。

```php:getRoleLabelAttribute
public function getRoleLabelAttribute()
{
    return $this->role->getLabel();
}
```