ソフトウェアでテーブル内の区分値を扱う方法は様々あるかと思いますが、個人的に扱いやすくできたので備忘録です。
本稿における区分値とは以下のような各種区分をDB上で扱いやすくするために、数値化などをおこなった値を指しています。
[
'gender' => [
0 => 'not known',
1 => 'male',
2 => 'female',
9 => 'not applicable'
]
]
[
'roles' => [
1 => 'developer',
2 => 'administrator',
3 => 'staff'
]
]
TL;DR
myclabs/php-enum を使用して、区分値をEnum化し、Model内でよしなにする。
Enumって何
Enumとは Enumerate の略で 列挙 を意味します。列挙型は、その名の通り定数を列挙したリストのようなものを作成することができ、区分値を扱う際によく利用されます。実装にもよりますが、値が同じでも異なるオブジェクトとして扱うことができるというメリットがあります。
例えば、前述の性別と権限の区分値を定数として実装した場合 MALE === DEVELOPER はどちらも値が 1 であるため true となり、バグを生み出してしまう可能性があります。
Enumは、言語によっては組み込まれている場合もありますが、PHPには組み込まれていない型であるため、PHPで使用したい場合は、自作するか外部ライブラリを使用する必要があります。
一般的に定数を扱うよりも処理が遅くなるので、定数はすべて列挙型というわけではなく、区分値などの適した場面で使用するのがよいかと思います。
区分値をEnum化する
Enumライブラリのインストール
PHPのEnumライブラリは複数ありますが、本稿ではGitHubのStar数が多い & 汎用的に扱えることから、myclabs/php-enumを使用します。
以下のコマンドでインストールします。
$ composer require myclabs/php-enum
Enumの作成
ファイルを作成する場所はお好みですが、今回は app 以下に Enums ディレクトリを作成し、そこに定義します。権限区分を例にEnumを作っていきます。
<?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
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 にも対応する区分値が代入されるようにします。
public function setRoleAttribute(Role $role)
{
$this->role_code = $role->getValue();
}
Modelから権限を判定できるようにする
せっかくなので、 php-enum に備わっている equals を利用してModel内で権限も判定できるようにしておきます。
equalsに渡す引数はメソッド呼び出しの形式であることに注意します。 () をつけないとクラス変数という扱いになり、正しい比較ができません。 メソッド呼び出しの形式にすることで、 Enum型を返却してくれます。
<?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
return [
'developer' => '開発者',
'administrator' => '管理者',
'staff' => '担当者'
];
これでコード内で __('myapp.developer') といったようにコールすることで、 '開発者' が取得できます。また、言語別にファイルを用意すればそれぞれの言語で取得できます。
Enum内に getLabel メソッドを作成する
メソッド名は何でもいいです。マジックメソッド __toString をオーバーライドしても良いのですが、デフォルトの挙動を破壊するのがなんとなく怖いので、今回は新たにメソッドを定義します。
getLabel では lang に定義した、Enumに対応する文字列を取得できるようなロジックを記述します。
今回はEnumのKeyを小文字にするだけで、langに定義したKeyになることと、今後 Role が追加されたとしても同じように対応できるだろうという考えで、EnumインスタンスのKeyをstrtolowerで小文字にすることでlangのキーを生成しています。単純に小文字にするだけでは対応できない場合は Enumのキー => langのキー のような連想配列を作成して取得するのがよさそうですね。
<?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を取得できます。
public function getRoleLabelAttribute()
{
return $this->role->getLabel();
}