結論として次の様に trait を書いて使うと便利です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | trait Findable { public static function find(string $key ): string { return constant( static :: class . '::' . $key )->value; } public static function tryFind(string $key ): ?string { try { return constant( static :: class . '::' . $key )->value; } catch (Error $_ ){ return null; } } } /*** 使用例 ***/ enum State: string { case INIT = '初期' ; case COMPLETED = '完了' ; use Findable; } $r = State::tryFind( 'INIT' ); // 初期 echo $r . "\n" ; $r = State::tryFind( 'INIT' ); // null echo $r . "\n" ; $r = State::find( 'COMPLETED' ); // 完了 echo $r . "\n" ; $r = State::find( 'PROCESSING' ); // Fatal error echo $r . "\n" ; |
以下は説明です。
PHP8.1 から Enum というクラスや連想配列に似たオブジェクトを定義できる言語機能が増えました。
これは次の様に Enum に定義したケースからケース自体の名前やケースに紐づいた値を呼び出せる機能です。
1 2 3 4 5 6 7 | enum State: string { case INIT = '初期' ; case COMPLETED = '完了' ; } echo State::INIT->name; // INIT echo State::INIT->value; // 初期 |
enum を用いることでプログラムの実行中に不正な値が入る可能性を防げます。これは次の様に動きます。
1 2 3 4 | function echoState(State $s ){ // State 型以外を引数に取れなくします echo $s ; } echoState( 'hoge' ); // Fatal error: Uncaught TypeError:... |
これにより、これまで定数を使わずに文字列を直に書いたり、プログラムの不具合であったりで不整合なデータが処理を続けることを防げます。
enum は便利ですが、上記の様なキーと値の対応を持つ enum について、配列の様に不定のキーから値を読み出す機能がPHPには組み込まれていません。次の様になります。
1 2 3 4 5 6 7 8 9 10 | 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: rfc:dynamic_class_constant_fetchにいい書き方のヒントがあっていい感じの書き方になったのでこのコードを紹介します。
コードと使用例は次です。trait にして各 Enum で use すれば find, tryFind メソッドでキーから値を呼び出せます。以前の記事と異なりどの Enum でも使えて計算量がO(1)で済んでるのが特徴です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | trait Findable { public static function find(string $key ): string { return constant( static :: class . '::' . $key )->value; } public static function tryFind(string $key ): ?string { try { return constant( static :: class . '::' . $key )->value; } catch (Error $_ ){ return null; } } } /*** 使用例 ***/ enum State: string { case INIT = '初期' ; case COMPLETED = '完了' ; use Findable; } $r = State::tryFind( 'INIT' ); // 初期 echo $r . "\n" ; $r = State::tryFind( 'INIT' ); // null echo $r . "\n" ; $r = State::find( 'COMPLETED' ); // 完了 echo $r . "\n" ; $r = State::find( 'PROCESSING' ); // Fatal error echo $r . "\n" ; |
実装内容は Enum がクラスであること、Enum の case が実質クラス定数であること、constant 関数で文字列から定数を呼び出せることを利用した、case を文字列で探して取得する処理です。
PHP: 列挙型の概要 – Manual
PHP: 列挙型の基礎 – Manual
PHP: constant – Manual
コードの内容は次の様にシンプルです。クラス定数を文字列から探す関数である constant を使って、実質クラス定数である case を呼び出しています。それだけです。
1 2 3 4 | public static function find(string $key ): string { return constant( static :: class . '::' . $key )->value; } |