13
4

More than 1 year has passed since last update.

【PHP8.3】 クラス定数とENUMを文字列から探せるようになる

Last updated at Posted at 2023-02-06

PHPのENUMにはひとつ重大な欠点がありまして、キーを文字列指定することができません。

どういうこと?

enum Suit:string{
    case Hearts = 'ハート';
    case Diamonds = 'ダイヤ';
    case Clubs = 'クラブ';
    case Spades = 'スペード';
}

echo Suit::Hearts->value; // ハート

$str = 'Diamonds'; // このキーから値を取りたい
echo Suit::{$str}->value; // syntax error

この制限のせいで、たとえばHTTPリクエストで送られてきた値からENUMを取り出すといったことができませんでした。

それっぽいメソッドBackedEnum::from()はありますが、これは値で検索するというものであり、完全に逆の機能です。

$str = 'クラブ';
echo Suit::from($str)->name; // Clubs

逆向きのBackedEnum::fromName()的なメソッドは何故か存在しません。
そんなわけで、これまではこの機能を実現するためにキーと値を逆にするというノウハウが流行していました。

これは動く
enum Suit:string{
    case ハート = 'Hearts';
    case ダイヤ = 'Diamonds';
    case クラブ = 'Clubs';
    case スペード = 'Spades';
}

echo Suit::ハート->value; // Hearts

これはもちろん冗談ですが。

ということで、これをどうにかするRFCが提出され受理されました。
PHP8.3からは、冒頭のSuit::{$str}が動作するようになります。

以下は該当のRFC、Dynamic class constant fetchの紹介です。

Dynamic class constant fetch

Proposal

PHPでは、メンバー名を調べる方法が多数存在します。

$$foo;          // 変数
$foo->$bar;     // プロパティ
Foo::${$bar};   // 静的プロパティ
$foo->{$bar}(); // メソッド
Foo::{$bar}();  // 静的メソッド
$foo::$bar;     // インスタンスの静的プロパティ
$foo::bar();    // インスタンスの静的メソッド

しかしこれには例外があり、それがクラス定数です。

class Foo {
    const BAR = 'bar';
}
$bar = 'BAR';

echo Foo::{$bar}; // syntax error

echo constant(Foo::class . '::' . $bar); // こっちは動く

この制限に意味があるものとは思えません。
このRFCでは、クラス定数に上記のような構文を取り入れることを提案します。

Semantics

Non-existent class constants

存在しないクラス定数に名前でアクセスするとErrorがthrowされます。
これは普通のクラス定数アクセスと同じ動作です。

class Foo {}

$bar = 'BAR';
echo Foo::{$bar}; // Error: Undefined constant Foo::BAR

{} expression type

中括弧の式の結果は文字列でなければなりません。
それ以外の場合はTypeErrorがthrowされます。

echo Foo::{[]};
// TypeError: Cannot use value of type array as class constant name

Order of execution

残念なことに、ルックアップの実行順はかなり直感に従いません。

function test($value) {
    echo $value . "\n";
    return $value;
}
 
class Foo implements ArrayAccess {
    public function __get($property) {
        echo 'Property ' . $property . "\n";
        return $this;
    }
 
    public function __call($method, $arguments) {
        echo 'Method ' . $method . "\n";
        return $this;
    }
 
    public static function __callStatic($method, $arguments) {
        echo 'Static method ' . $method . "\n";
        return static::class;
    }
 
    public function offsetGet($offset): mixed {
        echo 'Offset ' . $offset . "\n";
        return $this;
    }
    public function offsetExists($offset): bool {}
    public function offsetSet($offset, $value): void {}
    public function offsetUnset($offset): void {}
}
 
$foo = new Foo();
 
$foo->{test('foo')}->{test('bar')};
// foo
// bar
// Property foo
// Property bar
 
$foo->{test('foo')}()->{test('bar')}();
// foo
// Method foo
// bar
// Method bar
 
Foo::{test('foo')}()::{test('bar')}();
// foo
// Static method foo
// bar
// Static method bar
 
$foo[test('foo')][test('bar')];
// foo
// bar
// Offset foo
// Offset bar
 
// __getStaticは今のところないので動かない
Foo::${test('foo')}::${test('bar')};
// foo
// Static property foo
// bar
// Static property bar

プロパティや配列へのアクセスは、実際に操作を実行するより前に内部の式を評価します。
これはかなりテクニカルな理由によるものです。

プロパティや配列にアクセスしている間は、基本的にユーザコードを実行するべきではありません。
ポインタの再割り当てが起こり、ポインタが無効になってしまう可能性があるためです。

しかしこの問題は、クラス定数には当てはまりません。
そのため、よりシンプルで直感的なアプローチが採用されます。

Foo::{test('foo')}::{test('bar')};
// foo
// Class constant foo
// bar
// Class constant bar

Magic 'class' constant

マジック定数classも動的アクセスが可能になります。

namespace Foo;
 
$class = 'class';
echo Bar::{$class}; // Foo\Bar

Enums

この機能は、ENUMでも同様に動作します。

Future scope

この項目は将来の展望であり、このRFCには含まれません。

Interaction with ??

このRFCは、NULL合体演算子??の既存動作に影響を与えません。
すなわち、Foo::{$bar} ?? null;は、該当の定数が存在しなければErrorをthrowします。
他のタイプのメンバーアクセス同様、このエラーを抑制することはできますが、そのような挙動であるべきかはわかりません。

Vote

投票者の2/3の賛成で受理されます。

投票期間は2022/12/22から2023/01/05です。
本RFCは、賛成15反対4の賛成多数で可決されました。

感想

このRFC自体はENUMではなくクラス定数を目的としたものなのですが、その元となったIssueはENUMに対するものです。
そしてENUMはクラスを魔改造して作られたものです。

ということで、クラス定数を動的指定可能にすることでENUMも動的指定が可能になりました。
ENUMの使用がますます便利になりますね。

13
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
4