開発ブログ

株式会社Nextatのスタッフがお送りする技術コラムメインのブログ。

電話でのお問合わせ 075-744-6842 ([月]-[金] 10:00〜17:00)

  1. top >
  2. 開発ブログ >
  3. PHP >
  4. 【PHP8.1】PHP 8.1に実装されたEnum(列挙型)とEloquentの組み合わせを試してみた

【PHP8.1】PHP 8.1に実装されたEnum(列挙型)とEloquentの組み合わせを試してみた

こんにちは、モリです。
今回はついにPHP 8.1で追加されたEnum(列挙型)について扱いたいと思います。

PHPではEnumはこれまで組み込まれていなかったため、myclabs/php-enumのような外部ライブラリを利用している方が多いのではないかと思われます。
そこで今回はphp-enumを利用したEnumクラスをPHP 8.1 Enumに移行していく形で紹介します。
今回の例はEmployee(従業員)モデルが存在し、そのdepartment(所属部署)カラムがEnumであるという状況で、まずはEnumだけを見てます。

環境

  • PHP: 7.4.0→PHP: 8.1
  • Laravel Framework 6.20.6

php-enum の場合

class Department extends Enum
{
    private const GENERAL_AFFAIRS = 1;
    private const SALES = 2;
    private const HUMAN_RESOURCE = 3;
    private const ENGINEERING = 4;

    /** 表示用のテキストを取得 */
    public function getText(): string
    {
        switch ($this) {
            case self::GENERAL_AFFAIRS():
                return '総務部';
            case self::SALES():
                return '営業部';
            case self::HUMAN_RESOURCE():
                return '人事部';
            case self::ENGINEERING():
                return 'エンジニア部';
            default:
                throw new \LogicException("定義されていない部署です");
        }
    }
}
ではPHP 8.1 Enumに移行してみます。

PHP 8.1 Enumの場合

enum Department: int
{
    case GENERAL_AFFAIRS = 1;
    case SALES = 2;
    case HUMAN_RESOURCE = 3;
    case ENGINEERING = 4;

    /** 表示用のテキストを取得 */
    public function text(): string
    {
        return match($this) {
            self::GENERAL_AFFAIRS => '総務部',
            self::SALES => '営業部',
            self::HUMAN_RESOURCE => '人事部',
            self::ENGINEERING => 'エンジニア部',
        };
    }
}
このような定義の仕方になります。
そしてそれぞれの使い方は以下になります。
// php-enum
dump(Department::SALES());
/*
 App\EnumOld\Department {#1
   #value: 2
   -key: "SALES"
 }
 */
dump(Department::SALES()->getValue()); // 2
dump(Department::SALES()->getKey()); // SALES
dump(Department::SALES()->getText()); // 営業部

// PHP 8.1 Enum
dump(Department::SALES);
/*
 App\Enum\Department {#1
   +name: "SALES"
   +value: 2
 }
 */
dump(Department::SALES->value); // 2
dump(Department::SALES->name); // SALES
dump(Department::SALES->text()); // 営業部
メソッドではなくなったので()がなくなりました。value, nameプロパティを使うことででphp-enumと同じように値をとれます。
また、php-enumでよくお世話になったメソッドがあると思いますが、PHP 8.1 Enumでも利用したい場合はそのままphp-enumから流用すればよいでしょう。例えばenumの比較であるequalsは以下のようになります。
public function equals(self $enum): bool
{
    return $this->value === $enum->value
        && static::class === $enum::class;
}
その他のメソッドも置き換えてtrait化し、作成したEnumごとに再利用すればphp-enumと同じ感覚で実装できると思います。

Eloquent ModelでのEnumでの利用

今度はEnumをEloquent Modelにて利用することを考えます。
EmpoloyeeモデルのdepartmentカラムがEnumなので、ゲッター・ミューテータを定義しておくことでEnumオブジェクトとして値のやりとりができます。
// php-enum
class Employee extends Model
{
    /**
     * @param $value
     * @return Department
     */
    public function getDepartmentAttribute($value): Department
    {
        return new Department((int)$value);
    }

    /**
     * @param mixed $department
     */
    public function setDepartmentAttribute($department): void
    {
        $value = ($department instanceof Department) ? $department->getValue() : ((int)$department);
        $this->attributes['department'] = $value;
    }
}

$employee = \App\Models\Employee::find(1);
dump($employee->department->getValue()); //2
$employee->department = Department::ENGINEERING();
$employee->save();
dump($employee->department->getValue()); //4
このような形になります。そして、これをPHP 8.1 Enumに置き換えると下記になります。
// PHP 8.1 Enum
class Employee extends Model
{
    /**
     * @param $value
     * @return Department
     */
    public function getDepartmentAttribute($value): Department
    {
        return Department::from($value);
    }

    /**
     * @param mixed $department
     */
    public function setDepartmentAttribute($department): void
    {
        $value = ($department instanceof Department) ? $department->value : ((int)$department);
        $this->attributes['department'] = $value;
    }
}

$employee = \App\Models\Employee::find(1);
dump($employee->department->value); //2
$employee->department = Department::ENGINEERING;
$employee->save();
dump($employee->department->value); //4
ただの数値型として扱うと実装上生まれやすいバグですが、Enumオブジェクトとして扱うことで防ぐことができます。

しかし、カラムにEnumを含むモデルごとにいちいちゲッター・ミューテータを定義しないといけないため面倒です。
前述の通り、これまでの実装はLaravel6.xの環境での書き方でしたが、Laravel8.xでは属性のキャスト機能がPHP 8.1 Enumにも対応されて、ゲッター・ミューテータを定義する必要がなくなっています。
キャストの書き方は以下になります。
protected $casts = [
  'department'  => Department::class,
];
ゲッター・ミューテータを一切書かずにキャストを追加するだけで同じ動作をしてくれます。
これでかなりすっきりしますね。

最後に

今回は、PHP 8.1で追加されたEnumとEloquent Modelでの利用についてご紹介しました。
すでにphp-enumなどのライブラリを利用しているプロジェクトでわざわざ置き換える必要はないかもしれませんが、新たなプロジェクトでは使ってみたいですね。
以上となります。ありがとうございました。

 
TOPに戻る