クラス定数について、何も知らなかったんだなと思ったので、色々まとめました。
発端
PHPのAttributesでenumのcasesを展開させることできないの? 🤔 という質問を @tadsan さんにさせて貰っていたら颯爽と @hanhan1978 さんにそれRFCされてるよ!と教えて頂いたのが今日(昨日)のハイライト https://t.co/gF54cMuVyP
— katzchum (@katzchum) March 25, 2023
PHPerKaigi 2023の懇親会にて、AttributeでEnumのcasesを展開できない?みたいな質問をされたときに、自分はEnum関連だと最近RFCでてたなと思って、多分 PHP: rfc:dynamic_class_constant_fetch これですよという話をした。
Dynamic class constant fetch のおさらい
1 |
|
bar
と出力される
これは、8.3の新機能ですが、クラス定数を動的に参照できるだけであり、Enumのcasesのようなstaticメソッドの呼び出しに関する仕様ではない。
完全な勘違い、こんなことでは World Class PHPer にはなれない。
しかし、この件については、可能性のあるRFCが以前でていて…
Fetch property in const expressions
PHP: rfc:fetch_property_in_const_expressions
1 | enum Status: int |
const で Backed Enum の value を参照できるようになった。
これを使えば、上記例Values
のように、constにEnumとEnumの値を格納できる。このValues
はクラス定数なので、Attributeでも使える。
アトリビュートの引数はリテラル値か、定数式のみが指定できます。
実装例
これなら、AttributeでもEnum::cases()
相当の値を参照できる
ここまでのまとめ
dynamic class constant fetch
変数を使った動的な constant 呼び出しが出来る(RFC のタイトルままじゃん)
通常の constant 及び、 Enum の case が該当する
fetch property in const expressions
const において、プロパティの参照ができるようになった。
Enum::cases() のように、任意の式を const で展開することができない。
本当に必要だったもの
const において、staticメソッド呼び出しが出来れば、AttributeでもEnum::cases()がコールできる。
PHP: rfc:calls_in_constant_expressions
すでに似たようなコンセプトのRFCは一度出ていて、Withdrawn
なので取り下げられています。
このRFCでは、ホワイトリストに入っているグローバル関数であれば、const 内でコール可能です。
しかし、ここで諦めていてはしょうがない。とりあえず仕様としては駄目なことは分かった。PHPerたるもの、PHPのソースを読まねば。
用意するもの
calls in constant expressions はPoCが出ていたので、そのまま利用できます。
https://github.com/php/php-src/pull/5139/files
これをビルドすると
1 |
|
5
が出力される
組み込みのグローバル関数コールができる!
PoCの内容解説
Zend/zend_compile.c
zend_is_allowed_in_const_expr
この分岐をひとまず超えないと行けない- ast の種別に応じてコンパイル方法を変えている
※現状は ZEND_AST_STATIC_CALL
用の分岐がない
Zend/zend_ast.c
zend_ast_evaluate
- 実際に const の中身を評価している。ここにも
ZEND_AST_STATIC_CALL
用の分岐を増やす必要がある
グローバル関数同様に、static関数も事前状態が必要なく実行可能なので、改造すれば static 関数をコールすることは比較的簡単だろうと思われる。
ところで
そもそも、PHP の const ってなんなの?
クラス定数は、近年少しずつ機能追加されてきている
- スカラー演算 php5.6
- アクセス範囲 php7.1
- クラス定数へのfinalの利用 php8.1
- クラス定数でのbacked Enumプロパティ参照 php 8.2
- trait でのクラス定数の利用 php 8.2
- クラス定数の動的アクセス php 8.3
- クラス定数の型宣言 php 8.3
なんとなくだけど、徐々に利便性が上がってきている。
sji さんもおっしゃっていたけど、php の const は存在がフワッとしている。
このフワフワ感にもう少し意味付けがされてこないと、PHPのconstは理解できないのかもしれない。フワフワだからこそ、Enum::cases() も別に呼べてもいいじゃん?とも思えるし、フワフワだからこそ、これ以上 const で演算は許さんという気持ちも分かる。
もしかしたら、ここは PHPのconst考古学が必要なのかもしれない。
その他の情報ソース
pangudashu/php7-internal: PHP7内核剖析
EG(zend_constants)
ハッシュテーブルに値が格納されているので、参照自体は簡単。zend_compile.c
において、zend_compile_const_decl(zend_ast *ast)
でコンパイルされる。ZEND_DECLARE_CONST
命令でハッシュテーブルに格納される
現状では、定数はCONST型のみとなっているが、PHP8.3でクラス定数に型がつくはずなので、この辺は実装が変わってきていそう。
メモリ上の便宜とかあるのか?
C言語のように、メモリマップ上の静的領域に配置されて、プロセスまたいで使えるとかはない。名前に静的感があるが、メモリでの扱いは静的ではない。
まとめ
適当な受け答えをしていては、World Class PHPer にはなれないので、もっとちゃんとPHPの仕様とRFCを把握しような!