【PHP】enum を連想配列の要素の呼出しの様にキーから値を呼び出す

  • 2022年6月6日
  • PHP

 PHP8.1 では enum というクラスや連想配列に似たオブジェクトを定義できる言語機能が増えました。

PHP: 列挙型(Enum) – Manual

 これは次の様に enum に定義したケースからケース自体の名前やケースに紐づいた値を呼び出せる機能です。

enum State: string
{
    case INIT = '初期';
    case COMPLETED = '完了';
}
echo State::INIT->name; // INIT
echo State::INIT->value; // 初期

 enum を用いることでプログラムを実行中に不正な値が入る可能性を防げます。これは次の様に動きます。

function echoState(State $s){// State 型以外を引数に取れなくします
    echo $s;
}
echoState('hoge');// Fatal error: Uncaught TypeError:...

 これにより、これまで定数を使わずに文字列を直に書いたり、プログラムの不具合であったりで不整合なデータが処理を続けることを防げます。PHP8.1より前の場合、都度クラスを定義したり、バリデーションを作ったり、としていましたが enum を使うことでこれが簡単に作れるようになります。最終的な永続化先にルールを定義したりもしていましたが、これは enum 後も続けていた方が無難です。

 enum は便利ですが、上記の様なキーと値の対応を持つ enum について、配列の様に不定のキーから値を読み出すのは案外手間です。次の様になります。

enum State: string
{
    case INIT = '初期';
    case COMPLETED = '完了';
}
$key = 'INIT';
// 静的プロパティではなく定数
echo State::$key->name; // Fatal error: Uncaught Error: Access to undeclared static property
// 0,1,...の連番キーなので存在しない
echo State::cases()[$key];// Warning: Undefined array key "INIT"


 これの解決方法をいくつか紹介します。

PHP: 値に依存した列挙型(Backed Enum) – Manual

 一つ目はマッチ式を使う方法です。これは enum そのものを返すため他にも応用ができて便利です。しかしながら記述が気持ち多くなってしまいます。

enum State: string
{
    case INIT      = '初期';
    case COMPLETED = '完了';
    public static function find(string $key): ?self
    {
        return match ($key) {
            'INIT'      => self::INIT,
            'COMPLETED' => self::COMPLETED,
            default     => null,
        };
    }
}
$key = 'INIT';
echo State::find($key)->value;// 初期

 二つ目は foreach と cases メソッドを使う方法です。enum は cases メソッドからすべての要素を返してくれます。これはトレイトで使いまわせて便利ですが、ループ内で呼ばれると計算量が加速度的に増える問題があります。

trait Findable
{
    /**
     * foreach で線形探索するパターンです。
     */
    public static function findByForeach(string $key): ?static
    {
        foreach(static::cases() as $enum) {
            if($enum->name === $key) {
                return $enum;
            }
        }

        return null;
    }
}

enum State: string
{
    use Findable;

    case INIT = '初期';
    case COMPLETED = '完了';

}

$key = 'INIT';
echo State::findByForeach($key)->value;// 初期

 この問題は次の様に一時キャッシュ的に配列を使うと一応、解決します。

$someKeys = ['INIT','INIT','COMPLETED','INIT','COMPLETED'];
$map = [];
foreach($someKeys as $key) {
    $res       = $map[$key] ?? State::findByForeach($key);
    $map[$key] ??= $res;
    echo $res->value;
}

 色々試しましたが、正直このあたりの1対1の呼出しは連想配列が最も手軽で単純で高速な気がします。PHP8.2以降でもっと便利になるか、いいアイデアが必要です。enum である以上、プロパティを持てない制約はあった方がいいですし、クラス定数を可変で呼び出せるようにするなり専用の組み込みメソッドが増えるなりするのが現実的な気がします。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG